09.05.2013 Views

2004 - CONICET Santa Fe

2004 - CONICET Santa Fe

2004 - CONICET Santa Fe

SHOW MORE
SHOW LESS

You also want an ePaper? Increase the reach of your titles

YUMPU automatically turns print PDFs into web optimized ePapers that Google loves.

Universidad Nacional del Litoral<br />

Facultad de Ingeniería Química<br />

Departamento de Matemáticas<br />

Notas para los cursos de<br />

Computación y Programación<br />

con el lenguaje Pascal<br />

Néstor Aguilera<br />

Año <strong>2004</strong>


Índice general<br />

1. Preliminares 1<br />

1.1. ¿Qué esesto? ............................. 1<br />

1.2. Cómo usar este libro . . . . . . . . . . . . . . . . . . . . . . . . . 1<br />

1.3. Organización y convenciones usadas ................ 3<br />

1.3.1. Versión electrónica ...................... 3<br />

1.4. Para los viejos y/o insomnes . . . . . . . . . . . . . . . . . . . . . 4<br />

1.5. Sobre la versión <strong>2004</strong> . . . . . . . . . . . . . . . . . . . . . . . . . 6<br />

2. El primer contacto 7<br />

2.1. Un poco, muy poco, sobre cómo funciona la computadora .... 7<br />

2.2. Programas: edición, compilación, ejecución ............. 8<br />

2.3. El puntapié inicial . . . . . . . . . . . . . . . . . . . . . . . . . . 10<br />

2.4. Comentarios Bibliográficos ...................... 12<br />

3. Tipos de datos elementales 13<br />

3.1. Tipos, variables e identificadores .................. 13<br />

3.2. Tipos numéricos: entero y real .................... 16<br />

3.2.1. “Readln” . . . . . . . . . . . . . . . . . . . . . . . . . . . 17<br />

3.2.2. Funciones numéricas . . . . . . . . . . . . . . . . . . . . . 18<br />

3.2.3. La codificación de enteros y reales ............. 19<br />

3.3. Variables lógicas . . . . . . . . . . . . . . . . . . . . . . . . . . . 22<br />

3.4. Caracteres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23<br />

3.5. Comentarios Bibliográficos ...................... 24<br />

4. Tomando control 25<br />

4.1. “If” .................................. 26<br />

4.2. “While” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30<br />

4.3. “Repeat” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34<br />

4.4. “For” ................................. 35<br />

4.5. Ingresando muchos datos: “Eoln” .................. 37<br />

4.6. Comentarios Bibliográficos ...................... 39<br />

5. Aplicaciones 40<br />

5.1. Cálculo numérico elemental . . . . . . . . . . . . . . . . . . . . . 40<br />

5.1.1. Mezclando números grandes y pequeños . . . . . . . . . . 40<br />

5.1.2. Métodos iterativos: puntos fijos ............... 42<br />

5.1.3. El método babilónico . . . . . . . . . . . . . . . . . . . . . 45<br />

5.2. Números enteros y divisibilidad . . . . . . . . . . . . . . . . . . . 48


Pág. ii Índice general<br />

5.2.1. Ecuaciones diofánticas . . . . . . . . . . . . . . . . . . . . 48<br />

5.2.2. Máximo común divisor y el algoritmo de Euclides . . . . . 49<br />

5.2.3. Números primos . . . . . . . . . . . . . . . . . . . . . . . 52<br />

5.3. Problemas Adicionales ........................ 53<br />

5.4. Comentarios Bibliográficos ...................... 54<br />

6. Arreglos 55<br />

6.1. Dimensionamiento de arreglos . . . . . . . . . . . . . . . . . . . . 55<br />

6.2. Búsqueda Lineal ........................... 57<br />

6.3. Cribas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59<br />

6.4. Polinomios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62<br />

6.5. Problemas Adicionales ........................ 64<br />

6.6. Comentarios Bibliográficos ...................... 65<br />

7. Funciones y Procedimientos 66<br />

7.1. Funciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66<br />

7.2. El método de la bisección ...................... 70<br />

7.3. Procedimientos . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74<br />

7.4. Pasando por valor o por referencia . . . . . . . . . . . . . . . . . 77<br />

7.5. Comentarios Bibliográficos ...................... 80<br />

8. Todos juntos: arreglos, funciones y procedimientos 81<br />

8.1. Definiendo nuestros propios tipos de datos: “type” . . . . . . . . 81<br />

8.2. Ingreso e impresión de arreglos . . . . . . . . . . . . . . . . . . . 82<br />

8.3. La caja de herramientas . . . . . . . . . . . . . . . . . . . . . . . 84<br />

8.4. Arreglos multidimensionales . . . . . . . . . . . . . . . . . . . . . 85<br />

8.5. “Strings” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86<br />

8.6. Manejo elemental de archivos de texto . . . . . . . . . . . . . . . 86<br />

8.7. Problemas Adicionales ........................ 89<br />

8.8. Comentarios Bibliográficos ...................... 90<br />

9. Números Aleatorios y Simulación 91<br />

9.1. Números aleatorios . . . . . . . . . . . . . . . . . . . . . . . . . . 91<br />

9.2. Aplicaciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92<br />

9.3. Problemas Adicionales ........................ 94<br />

10.Búsqueda y clasificación 96<br />

10.1. Búsqueda lineal con centinela . . . . . . . . . . . . . . . . . . . . 96<br />

10.2. Búsqueda binaria ........................... 98<br />

10.3. Métodos elementales de clasificación . . . . . . . . . . . . . . . . 101<br />

10.4. Registros (Records) . . . . . . . . . . . . . . . . . . . . . . . . . . 105<br />

10.5. Problemas Adicionales ........................108<br />

10.6. Comentarios Bibliográficos ......................109<br />

11.Recursión 110<br />

11.1. Funciones y procedimientos definidos recursivamente . . . . . . . 110<br />

11.2. Los Grandes Clásicos de la Recursión . . . . . . . . . . . . . . . . 113<br />

11.3. Problemas Adicionales ........................116<br />

11.4. Comentarios Bibliográficos ......................118


Índice general Pág. iii<br />

12.Generando objetos combinatorios 119<br />

12.1. Pilas y colas ..............................119<br />

12.2. Generando Subconjuntos . . . . . . . . . . . . . . . . . . . . . . . 121<br />

12.3. Generando permutaciones ......................124<br />

12.4. Árboles binarios ordenados . . . . . . . . . . . . . . . . . . . . . 126<br />

12.5. Problemas Adicionales ........................131<br />

12.6. Comentarios Bibliográficos ......................132<br />

13.Grafos y árboles 133<br />

13.1. Representación de grafos en la computadora ............136<br />

13.2. Recorriendo un grafo . . . . . . . . . . . . . . . . . . . . . . . . . 137<br />

13.3. Recorrido a lo ancho y en profundidad ...............139<br />

13.4. Camino más corto: Dijkstra . . . . . . . . . . . . . . . . . . . . . 141<br />

13.5. Mínimo árbol generador: Prim . . . . . . . . . . . . . . . . . . . 145<br />

13.6. Problemas Adicionales ........................148<br />

13.6.1. El algoritmo de Kruskal . . . . . . . . . . . . . . . . . . . 149<br />

13.7. Comentarios Bibliográficos ......................154<br />

14.Punteros y listas encadenadas 155<br />

14.1. Punteros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 155<br />

14.2. Listas encadenadas . . . . . . . . . . . . . . . . . . . . . . . . . . 157<br />

14.3. Otras estructuras dinámicas . . . . . . . . . . . . . . . . . . . . . 160<br />

14.4. Problemas Adicionales ........................162<br />

14.5. Comentarios Bibliográficos ......................162<br />

15.Más problemas 163<br />

15.1. Polinomios interpoladores de Lagrange ...............163<br />

15.2. Cuenta de Goldbach . . . . . . . . . . . . . . . . . . . . . . . . . 164<br />

15.3. El juego de “la generala” ......................164<br />

15.4. Visibilidad en el plano ........................164<br />

15.5. Aproximando e con dados ......................164<br />

15.6. Generando “manos” aleatoriamente ................165<br />

15.7. Clasificación por fusión . . . . . . . . . . . . . . . . . . . . . . . 166<br />

15.8. Media, mediana y moda . . . . . . . . . . . . . . . . . . . . . . . 168<br />

15.9. Números de Fibonacci: Zeckendof ..................168<br />

15.10. La Cola de Supermercado . . . . . . . . . . . . . . . . . . . . . 168<br />

15.11. La Producción de la Fábrica ....................169<br />

15.12. Pasos en búsqueda binaria . . . . . . . . . . . . . . . . . . . . . 169<br />

15.13. El Juego de Nim . . . . . . . . . . . . . . . . . . . . . . . . . . 169<br />

15.14. Comentarios Bibliográficos . . . . . . . . . . . . . . . . . . . . . 171<br />

A. Programas mencionados 173<br />

Problema 2.2: holamundo . . . . . . . . . . . . . . . . . . . . . . . . . 173<br />

Problema 3.2: sumardos . . . . . . . . . . . . . . . . . . . . . . . . . . 173<br />

Problema 3.3: leerdato . . . . . . . . . . . . . . . . . . . . . . . . . . . 173<br />

Problema 3.4: raiz .............................174<br />

Problema 3.7: enteroareal . . . . . . . . . . . . . . . . . . . . . . . . . 174<br />

Problema 3.13: segundos . . . . . . . . . . . . . . . . . . . . . . . . . . 175<br />

Problema 3.16: positivo . . . . . . . . . . . . . . . . . . . . . . . . . . . 175<br />

Problema 3.18: caracteres1 . . . . . . . . . . . . . . . . . . . . . . . . . 175


Pág. iv Índice general<br />

Problema 4.2: valorabsoluto ........................176<br />

Problema 4.4: comparar . . . . . . . . . . . . . . . . . . . . . . . . . . 176<br />

Problema 4.5: caracteres2 .........................176<br />

Problema 4.11: resto . . . . . . . . . . . . . . . . . . . . . . . . . . . . 177<br />

Problema 4.12: tablaseno . . . . . . . . . . . . . . . . . . . . . . . . . . 177<br />

Problema 4.13: gauss . . . . . . . . . . . . . . . . . . . . . . . . . . . . 178<br />

Problema 4.16: cifras . . . . . . . . . . . . . . . . . . . . . . . . . . . . 179<br />

Problema 4.17: epsmin ...........................179<br />

Problema 4.18: potencia . . . . . . . . . . . . . . . . . . . . . . . . . . 180<br />

Problema 4.22: eolnprueba .........................180<br />

Problema 4.23: sumardatos .........................181<br />

Problema 5.10: babilonico .........................181<br />

Problema 5.15: euclides ...........................182<br />

Problema 5.20: factoresprimos . . . . . . . . . . . . . . . . . . . . . . . 183<br />

Problema 6.1: unidades ...........................184<br />

Problema 6.2: busquedalineal ........................184<br />

Problema 6.5: eratostenes .........................186<br />

Problema 6.7: flaviojosefo1 .........................187<br />

Problema 7.1: potencias2 . . . . . . . . . . . . . . . . . . . . . . . . . . 188<br />

Problema 7.2: potencias3 . . . . . . . . . . . . . . . . . . . . . . . . . . 188<br />

Problema 7.3: biseccion ...........................189<br />

Problema 7.6: tablaseno2 . . . . . . . . . . . . . . . . . . . . . . . . . . 191<br />

Problema 7.7: intercambio .........................192<br />

Problema 8.2: maximocaracter . . . . . . . . . . . . . . . . . . . . . . . 192<br />

Problema 8.8: deconsolaaarchivo ......................194<br />

Problema 8.8: dearchivoaconsola ......................195<br />

Problema 9.1: dado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 195<br />

Problema 9.2: dados . . . . . . . . . . . . . . . . . . . . . . . . . . . . 196<br />

Problema 12.6: arbolbinario ........................197<br />

Problema 13.4: anchoprimero . . . . . . . . . . . . . . . . . . . . . . . 199<br />

Problema 13.8: dijkstra ...........................202<br />

Problema 14.1: listas . . . . . . . . . . . . . . . . . . . . . . . . . . . . 206<br />

Problema 14.9: grafos ...........................209<br />

B. Sobre los compiladores Pascal 214<br />

C. Breve referencia de Pascal 215<br />

C.1. Resumen de operadores . . . . . . . . . . . . . . . . . . . . . . . 215<br />

C.1.1. Operadores aritméticos . . . . . . . . . . . . . . . . . . . 215<br />

C.1.2. Operadores relacionales . . . . . . . . . . . . . . . . . . . 215<br />

C.1.3. Operadores lógicos ......................215<br />

C.2. Identificadores estándares ......................215<br />

C.3. Nombres Reservados .........................217<br />

D. Algunas notaciones y símbolos usados 218<br />

D.1. Lógica . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 218<br />

D.2. Conjuntos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 218<br />

D.3. Números: conjuntos, relaciones, funciones .............219<br />

D.4. Números importantes en programación . . . . . . . . . . . . . . . 220<br />

D.5. Generales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 220


Índice general Pág. v<br />

Bibliografía 221<br />

Índice de figuras 223<br />

Índice de cuadros 224<br />

Índice alfabético 225


Capítulo 1<br />

Preliminares<br />

1.1. ¿Qué esesto?<br />

Algunos se enloquecen por la cerveza, otros prefieren vino, y también están<br />

los abstemios. Pero todos estamos de acuerdo en que la computadora ocupa, y<br />

cada vez más, un lugar importante en nuestras vidas.<br />

Decimos esto pensando en internet, el acceso a la información, la cuenta<br />

del banco y los impuestos, multimedios como gráficos, sonidos, películas y juegos<br />

(videos), y siguiendo con una larga lista que, sin embargo, difícilmente incluirá<br />

resolución de problemas matemáticos.<br />

Y nuestras creencias están de acuerdo con nuestras experiencias. Los primeros<br />

encuentros con la computadora y sus aplicaciones tienen que ver con hacer<br />

“click” con el “ratón” para acceder a juegos, “browsers” y el correo electrónico.<br />

Después aprendemos algo del funcionamiento del sistema operativo, procesadores<br />

de texto y planillas de cálculo, y el software que se use en nuestro trabajo.<br />

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

por personas, ingenieros y técnicos de sistemas, y programadores. Consecuentemente,<br />

el primer curso de programación usualmente está orientado hacia las<br />

aplicaciones de sistemas informáticos, por ejemplo hacia el manejo de archivos<br />

con vistas a bases de datos, lo cual es muy comprensible.<br />

La propuesta aquí es un tanto diferente: hacer el primer curso de programación<br />

pensando en las aplicaciones matemáticas. Después de todo, el nombre<br />

“computadora” tiene que ver más con matemáticas que con “informática”.<br />

En el libro se va entrelazando la introducción de los elementos de programación<br />

estructurada con la resolución de problemas de análisis y cálculo numérico,<br />

teoría de números, combinatoria y grafos, sirviendo tanto de introducción de<br />

algunos temas como de repaso y fortalecimiento de otros.<br />

Quedan aparte temas de álgebra lineal, ya que la resolución numérica de<br />

problemas lineales requiere un estudio cuidadoso de los errores, que está fuera<br />

del propósito introductorio del libro.<br />

1.2. Cómo usar este libro<br />

Uno puede darse una idea de cómo cocinar mirando un libro de cocina, pero<br />

seguramente la experiencia es mucho más completa y satisfactoria si se tienen


Pág. 2 Preliminares<br />

los ingredientes, las cacerolas y la misma cocina. Del mismo modo, se puede<br />

apreciar qué es la programación mirando un libro, pero nosotros supondremos<br />

que el lector dispone además de una computadora y un compilador Pascal con<br />

los que trabajar.<br />

Así como el aprendiz aprende observando al artesano, en programación es<br />

muy importante copiar (y tomar ideas y estructuras de) programas ya hechos,<br />

aunque a veces no se entienda exactamente del porqué detalocualsintaxis.<br />

Idealmente quien trabaje con los contenidos de este libro copiará (preferentemente<br />

de un diskette o de internet) los programas presentados, y hará variaciones<br />

ocopiarápartesdeunoomás de ellos para resolver los distintos problemas.<br />

Muchos principiantes se ponen ansiosamente a tipear un programa sin tener<br />

un plan específico, quizás por su experiencia previa con la computadora. Una<br />

buena parte de los problemas que planteamos no son sencillos de resolver, y<br />

seguramente no se resuelven probando con distintos “botones”. Más aún, varios<br />

de los problemas no necesitan de la computadora.<br />

Quien quiera sacar provecho de este libro deberá hacer uso del lápiz y papel<br />

para trazarse un plan de resolución antes de copiar o modificar los programas,<br />

pero para eso debe entender primero qué es lo que hacen las distintas partes y<br />

—por sobre todo— a dónde se quiere llegar.<br />

La adquisición de conocimientos no es gratuita y requiere esfuerzo y tiempo,<br />

y las equivocaciones forman parte inevitable de este proceso:<br />

¡habrá que estar dispuesto a pasar un buen rato en<br />

la silla!<br />

Aún cuando no podamos resolver un problema en programación o en matemáticas,<br />

es poco útil que alguien nos cuente la solución si antes no hemos<br />

hecho un trabajo propio: de este modo podremos apreciar, e inclusive criticar,<br />

la que nos proponen.<br />

En algunos problemas se incluyen “sugerencias” para la resolución, que están<br />

puestas como orientación cuando se está “perdido”. La recomendación es “tapar”<br />

la sugerencia, y recurrir a ella en segunda instancia o cuando ya se ha<br />

resuelto el problema para ver si hay otras posibilidades de resolución.<br />

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

no hay una única forma de hacer los programas o resolver los problemas. Lo<br />

presentado es sólo una posibilidad.<br />

A algunos les parecerá que las sugerencias son oscuras o escasas, a otros<br />

les parecerá que el material presentado es excesivo, y habrá otros que querrán<br />

resolver más problemas: para éstos se incluye una sección de “Problemas Adicionales”<br />

en los capítulos más avanzados, así como todo el capítulo 15.<br />

A todos les recomendamos los libros de Engel [4], Wirth [15, 16] —autordel<br />

lenguaje que usamos, Pascal— y Jensen y Wirth [7] para temas específicos de<br />

Pascal, que forman la base sobre la cual está hecho este libro.<br />

En fin, habrá algunos que querrán seguir avanzando aún más, quizás después<br />

de terminar este libro, y una posibilidad muy interesante es tomar problemas<br />

de las competencias estudiantiles de la ACM (Association for Computing Ma-


1.3. Organización y convenciones usadas Pág. 3<br />

chinery (1) ), en las que los representantes de nuestro país vienen participando<br />

exitosamente desde hace varios años.<br />

1.3. Organización y convenciones usadas<br />

En los capítulos 2 a 15 se presentan los temas y problemas, agrupados en<br />

secciones y a veces subsecciones. Los problemas están numerados comenzando<br />

con 1 en cada capítulo, de modo que el “problema 4.5” se refiere al problema 5<br />

del capítulo 4. De modo similar, la “sección 3.2” se refiere a la sección 2 del<br />

capítulo 3.<br />

En muchos de los problemas se mencionan programas completos, que se han<br />

puesto juntos en el apéndice A, al que le sigue información sobre compiladores<br />

Pascal (apéndice B) disponibles en internet. A modo de glosarios, finalizamos<br />

con una breve referencia de Pascal (apéndice C) y notaciones usadas en el libro<br />

(apéndice D).<br />

A veces intercalaremos texto entre los problemas, por lo que para indicar el<br />

fin del enunciado de un problema colocamos el signo ✄, que puede leerse como<br />

“la cortamos acá”.<br />

Intercalados entre texto y enunciados de problemas, hay algunas notas y<br />

comentarios, en tipo de letra más chico para no distraer demasiado del texto<br />

principal, y puede omitirse su lectura.<br />

En los comentarios, en itálica, se hacen referencias históricas, orientadoras,<br />

curiosidades, etc. Son de la forma<br />

Esto es un comentario.<br />

Por otra parte, las notas son en general de índole más técnica, y son de la<br />

forma<br />

✎ Esto es una nota.<br />

Usamos otros tipos de letra para indicar los nombres de los programas, así,<br />

mientras que lo que escribiremos en la computadora está indicado en “monotipo”,<br />

así, algunas veces entre comillas dobles, para recalcar algún fragmento,<br />

“ como éste ”, reservando las comillas simples para caracteres, como ‘ a ’.<br />

Siguiendo la tradición norteamericana, la computadora expresa los números<br />

poniendo un “punto” decimal en vez de la “coma”, y para no confundirnos<br />

seguiremos esa práctica en todo el texto. Así, 1.589 es un númeroentre1y2,<br />

mientras que 1589 es un número entero, mayor que mil.<br />

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

“ ~n ”) al escribir los programas o mostrar resultados por pantalla, de modo que<br />

en la escritura de códigos los obviaremos (escribiendo “ a ”o“ni ”).<br />

En fin, en el texto indicamos con el pulsado de la tecla “Retorno”<br />

(o “Return” en teclados ingleses) para iniciar un nuevo renglón. Dependiendo<br />

del sistema operativo, es posible que deba pulsarse en cambio la tecla “Intro”<br />

(o “Enter” en inglés).<br />

1.3.1. Versión electrónica<br />

La versión electrónica (2) en formato “pdf” tiene algunas ventajas:<br />

(1) http://icpc.baylor.edu/icpc/<br />

(2) Disponible en http://math.unl.edu.ar/~aguilera/libro<strong>2004</strong>


Pág. 4 Preliminares<br />

• Se puede leer e imprimir desde distintas plataformas (Unix, Windows, etc.)<br />

usando el software gratuito Adobe Reader (anteriormente Adobe Acrobat<br />

Reader) (3) .<br />

• Es muy fácil moverse dentro del documento, subiendo o bajando una página<br />

o ir a una página determinada, y también se puede ver en distintos<br />

tamaños.<br />

• Se puede buscar dentro del documento algún fragmento de texto. . . tratando<br />

de evitar acentos como en “á”, tildes como en “ñ” o “ligaduras”<br />

como en “fi”.<br />

• Eligiendo la “herramienta de texto”, marcada con una gran “T”, es posible<br />

copiar algún fragmento de texto desde un archivo “pdf” a otro documento<br />

hecho con otro software. Esto es especialmente útil para copiar partes de<br />

programas.<br />

• Tiene capacidades de “hipertexto”: referencias en color pueden seleccionarse<br />

para moverse en el documento o internet, y es sencillo volver al lugar<br />

anterior.<br />

Pero. . .<br />

– al imprimir en blanco y negro los colores se ven como grises,<br />

– puede suceder que la posición en la página a la que se llegue esté un<br />

poco corrida hacia arriba o abajo, lo que es especialmente confuso<br />

cuando justo hay un cambio de página,<br />

– o peor, que directamente nos lleve al lugar incorrecto: ¡no sólo los<br />

alumnos y los profes hacen programas imperfectos!<br />

1.4. Para los viejos y/o insomnes<br />

El núcleo del libro es la resolución de problemas para desarrollar el pensamiento<br />

matemático y las conexiones con el pensamiento algorítmico, pasando<br />

de abstracciones al resultado concreto de un programa para la computadora.<br />

Hemos tratado de ubicarnos entre dos extremos. Por un lado el uso de la<br />

computadora como un fin y no como un medio, donde lo importante es la computadora<br />

relegando a un plano secundario, o aún ignorando, las matemáticas.<br />

Por otro lado, el aprendizaje de sistemas avanzados con los que “apretando botones”<br />

obtenemos resultados, muchas veces sin saber de dónde salen ni cuán<br />

confiables son (o de qué estamos hablando).<br />

Y seguramente que queremos apartarnos lo másposibledelapeordetodas<br />

las propuestas, lamentablemente muy difundida: el aprendizaje rutinario de<br />

técnicas de dudoso valor que son rápidamente olvidadas.<br />

Esta relación entre abstracto y concreto se realimenta al usar la computadora<br />

como banco experimental para encontrar patrones, llegar a conjeturas y a su<br />

eventual demostración matemática.<br />

Interpuestos más que nada como comentarios, se tocan temas de eficiencia<br />

y complejidad de algoritmos, pero no se incluyen temas de corrección de<br />

programas.<br />

(3) Disponible en http://www.adobe.com/products/acrobat/readermain.html


1.4. Para los viejos y/o insomnes Pág. 5<br />

Una característica del libro, además de los temas que se presentan, es la<br />

inclusión de muchos programas acabados, pues en programación —mucho más<br />

que en matemáticas— se aprende mirando lo que han hecho otros, y es inclusive<br />

deseable el “reuso” de partes ya hechas. Sin embargo los programas presentados<br />

difícilmente puedan considerarse “estado del arte”, y se han sacrificado la<br />

eficiencia y la brevedad tratando de hacer una presentación clara.<br />

También sólo se usa seudo-código en los capítulos más avanzados (y en mínima<br />

medida), a contra corriente de otros cursos introductorios, con la convicción<br />

de que es muy difícil entender —e imposible concretar— qué seestádiciendo en seudo-código. Es como tratar de aprender una síntesis de los idiomas antes<br />

de aprender a hablar en alguno (4) .<br />

En el libro usamos el lenguaje Pascal. Considerado “obsoleto” por distintas<br />

razones, tiene la gran ventaja de haber sido diseñado especialmente para la<br />

enseñanza de programación por uno de los grandes autores, N. Wirth, y brinda<br />

la posibilidad de una clara formulación de los programas y —mucho más<br />

importante— una sólida base para el futuro.<br />

Habiendo elegido Pascal, hemos tratado de ceñirnos lo más posible a su<br />

estándar pues debe conocerse y apreciarse la importancia de la portabilidad y<br />

consistencia entre diferentes sistemas operativos.<br />

Los capítulos 2, 3, 4, 6, 7, 8, 10 y 11 son más o menos comunes a todos los<br />

cursos de programación, aunque —como mencionamos— hay un fuerte énfasis<br />

hacia las matemáticas dejando de lado la informática. En cambio, los capítulos 5<br />

y 9 cubren temas de matemáticas (teóricas o aplicadas) que normalmente no se<br />

dan.<br />

Aunque pilas y colas es un tema normalmente incluido en cursos de programación,<br />

la perspectiva que damos en el capítulo 12 es bastante distinta:<br />

allí trabajamos con objetos combinatorios, como subconjuntos, permutaciones<br />

yárboles binarios, donde aparece verdaderamente la fuerza de recursión.<br />

Lossiguientesdoscapítulos toman rumbos distintos y son bastante independientes<br />

entre sí. El capítulo 13 está pensado para aquéllos que ya hayan visto<br />

un curso de matemática discreta o teoría de grafos, implementando (en forma<br />

elemental) los primeros algoritmos de grafos. En cambio, el capítulo 14 muestra<br />

las posibilidades de las listas encadenadas, liberándonos de algunas ataduras de<br />

los arreglos.<br />

En el capítulo final hay algunos temas y problemas, algunos más avanzados,<br />

que hemos preferido separar del cuerpo principal del libro.<br />

En la redacción se puede notar la influencia de los libros de Kernighan y<br />

Ritchie [10] yWirth[16] en lo referente a programación, y de Engel [4] yGentile<br />

[5] en cuanto a basar el curso en resolución de problemas (y varios de los<br />

temas considerados). Por otro lado, hemos seguido el estándar Pascal según [7].<br />

Al final de algunos capítulos damos referencias bibliográficas sobre temas y<br />

problemas particulares, pero muchos de ellos pertenecen al “folklore” y es difícil<br />

determinar el origen.<br />

En cuanto a las notas de carácter histórico, muchas han sido basadas en el<br />

material de http://www-groups.dcs.st-and.ac.uk/˜history<br />

(4) Los más viejos recordamos el fracaso de la “matemática moderna”.


Pág. 6 Preliminares<br />

1.5. Sobre la versión <strong>2004</strong><br />

Estas notas se van modificando añoaaño en base a la experiencia que se va<br />

ganando en el dictado del curso. Algunos temas se han eliminado o cambiado,<br />

mientras que otros se han agregado. En particular, para la versión <strong>2004</strong> hemos<br />

agregado muchos más gráficos, y por lo tanto bastante más páginas, así como<br />

el hipertexto en la versión electrónica. También hemos agregado un índice<br />

alfabético que seguramente dista de ser perfecto.<br />

Por supuesto, hemos tratado de corregir los errores encontrados (tipográficos<br />

o conceptuales) en notas de años anteriores, por lo cual debo agradecer<br />

especialmente a Luis Bianculli, Jorge D’Elía y Egle Haye.


Capítulo 2<br />

El primer contacto<br />

2.1. Un poco, muy poco, sobre cómo funciona<br />

la computadora<br />

Conceptualmente, la computadora es una máquina que toma o accede a<br />

datos, los procesa y devuelve resultados. Los datos o entradas y los resultados<br />

o salidas pueden ser simples como números o letras, o mucho más complicados<br />

como una matriz o una base de datos (1) , que podemos esquematizar como<br />

entrada procesamiento salida<br />

En el modelo de computadora con el que trabajaremos (o de von Neumann),<br />

pensaremos que el procesamiento está a cargo de una única unidad, llamada<br />

CPU por “Central Processing Unit” o Unidad Central de Procesamiento, que<br />

accede los datos y retorna los resultados secuencialmente, es decir, de a uno por<br />

vez, y los datos a los que accede se guardan en una lugar denominado memoria.<br />

John von Neumann (1903–1957) se interesó inicialmente en lógica, teoría<br />

de conjuntos, de la medida, y mecánica cuántica, tocando luego temas de análisis<br />

funcional, teoría ergódica, siendo fundador de la teoría de juegos. En sus<br />

últimos años también tuvo influencia decisiva en ecuaciones en derivadas parciales<br />

y en teoría de autómatas, en la que sintetizó sus conocimientos e ideas<br />

de lógica y grandes computadoras electrónicas.<br />

En los programas que haremos normalmente nos comunicaremos con la computadora<br />

entrando los datos con el teclado y recibiendo los resultados en la pantalla,<br />

refiriéndonos en general como terminal o consola al conjunto combinado<br />

de teclado y pantalla. Estos datos que entramos o recibimos no son directamente<br />

procesadosporlaCPU,sinoquesontransferidosaodesdelamemoriamediante<br />

la misma CPU u otro procesador dedicado.<br />

Un esquema del movimiento de datos entre periféricos (consola, discos, impresora,<br />

etc.), memoria y CPU está indicado en la figura 2.1.<br />

Nos imaginaremos que la memoria, en donde se almacenan los datos, está<br />

constituida por muchas cajitas pequeñas llamadas bits por “Binary Digit”<br />

(1) Es como pensar en una máquina de hacer chorizos: ponemos los ingredientes (mezcla,<br />

tripa, etc.), y después de dar vuelta la manija tenemos los chorizos.


Pág. 8 El primer contacto<br />

consola<br />

pantalla<br />

teclado<br />

discos<br />

impresora<br />

otros<br />

memoria<br />

(datos)<br />

CPU<br />

(procesamiento)<br />

Figura 2.1: Esquema de transferencia de datos en la computadora.<br />

odígito binario, en cada una de las cuales sólo se puede guardar un 0 o un<br />

1.Puestoqueestacajaesdemasiadopequeña para guardar información más<br />

complicada que “sí/no” o “blanco/negro”, los bits se agrupan en cajas un poco<br />

más grandes llamadas bytes, que generalmente tienen 8 bits, conceptualmente<br />

alineados, puesto que queremos que 00001111 sea distinto de 11110000. Ver<br />

esquema en la figura 2.2.<br />

<br />

un byte<br />

<br />

1 0 1 0 1 0 0 1<br />

bits<br />

Figura 2.2: Bits y byte.<br />

Problema 2.1. Suponiendo que un byte tenga 8 bits:<br />

a) ¿Cuántas “ristras” distintas de 0’s y 1’s puede tener? Sugerencia: hacer la<br />

cuenta primero para un byte de 1 bit, luego para un byte de 2 bits, luego<br />

para un byte de 3 bits,. . .<br />

b) Si no importara el orden de los bits que forman el byte, y entonces 00001111,<br />

11110000, 10100101 fueran indistinguibles entre sí, ¿cuántos elementos distintos<br />

podría contener un byte? Sugerencia: si el byte tiene 8 bits puede ser<br />

que hayan 8 ceros y ningún uno, o 7 ceros y 1 uno, o. . . ✄<br />

A su vez, para las computadoras más recientes, estas unidades resultan demasiado<br />

pequeñas para alimentar a la CPU, por lo que los bits o bytes se agrupan<br />

formando cajas de, por ejemplo, 32, 64 o 128 bits (usualmente potencias de 2),<br />

siempre conceptualmente alineadas.<br />

2.2. Programas: edición, compilación, ejecución<br />

Por supuesto, queremos que la computadora “haga algo” con los datos que<br />

le damos, pero tenemos que darle instrucciones sobre cómo hacerlo. El conjunto<br />

de instrucciones y datos para realizar determinada tarea es lo que llamaremos<br />

programa, y los mismos programas pueden considerarse como un tipo especial<br />

de datos.


2.2. Programas: edición, compilación, ejecución Pág. 9<br />

En particular, el sistema operativo de la computadora es un programa que<br />

alimenta constantemente a la CPU, y le va a indicar, por ejemplo, que ejecute<br />

(o corra) nuestro programa, leyendo las instrucciones que contiene.<br />

Los lenguajes de programación son “abstracciones” que nos permiten escribir<br />

las instrucciones de un programa de forma que un ser humano puede entender<br />

más fácilmente que ristras de ceros y unos. Las instrucciones para la máquina<br />

se escriben como sentencias —de acuerdo a las reglas del lenguaje— en lo que<br />

se llama programa fuente.<br />

Hay distintos tipos de lenguajes de programación, cada uno con sus defectos<br />

y virtudes. Dentro de los que más se usan en matemáticas, están (en orden más o<br />

menos cronológico) Fortran,“C”,Pascal, “C++”, y otros integrados a sistemas<br />

con posibilidades gráficas y/o simbólicas como Matlab, Maple o Mathematica. En<br />

estas notas usaremos el lenguaje Pascal, que ha sido diseñado para la enseñanza<br />

de programación.<br />

N. Wirth dio el nombre de Pascal al lenguaje en honor al matemático<br />

francés Blaise Pascal (1623–1662), quien fue uno de los primeros en desarrollar<br />

una calculadora mecánica, cuando tenía unos 20 años.<br />

Sin embargo hubo muchas otras calculadoras antes, como las computadoras<br />

lunares y planetarias del astrónomo iraní al-Kashi (1393–1449), o la de W.<br />

Schickard (1592–1635) que multiplicaba y dividía, mientras que la de Pascal<br />

(construida entre los años 1642–1644) sólo sumaba y restaba.<br />

Luego de elegir un lenguaje de programación, al desarrollar un programa lo<br />

usual es primero escribir el programa fuente (el que nosotros entendemos) con<br />

un editor de textos en la computadora, eventualmente guardando lo escrito en<br />

el disco rígido o un diskette.<br />

El programa fuente debe ser traducido a algo que la CPU pueda entender,<br />

es decir las famosas ristras de 0’s y 1’s. Este proceso se llama compilado, dando<br />

por resultado un programa ejecutable, que es el que en realidad va a usar la<br />

computadora.<br />

✎ Esta descripción basta para nuestros propósitos, en realidad todo el proceso<br />

requiere otros pasos intermedios que en nuestro caso serán hechos<br />

automáticamente.<br />

✎ Algunos lenguajes son interpretados, es decir no existe la compilación y no<br />

se crea el ejecutable.<br />

En la mayoría de los casos —aún para gente experimentada— habrá problemas<br />

en la compilación (por ejemplo, por errores de sintaxis), o al ejecutar<br />

el programa los resultados no serán los esperados. Esto da lugar a un ciclo de<br />

trabajo esquematizado en la figura 2.3.<br />

Editar fuente Compilar ejecutable Ejecutar<br />

Corregir<br />

Figura 2.3: Esquema del desarrollo de un programa.<br />

En fin, al momento de usar el programa ejecutable, el sistema operativo lo<br />

aloja en algún lugar disponible de la memoria, quedando un esquema como el<br />

de la figura 2.4.


Pág. 10 El primer contacto<br />

Memoria<br />

programa<br />

ejecutable<br />

datos<br />

instrucciones<br />

Figura 2.4: Esquema del programa ejecutable en la memoria.<br />

2.3. El puntapié inicial<br />

Para lo que sigue, será conveniente ir mirando el primer programa Pascal<br />

con el que trabajaremos: el programa holamundo (pág. 173).<br />

En Pascal, todos los programas (fuentes) se dividen en dos partes o cuerpos.<br />

En la primera, a veces llamada de declaraciones, se coloca el nombre del<br />

programa mediante “ program nombre(input,output)” yotrassentenciasque<br />

iremos viendo.<br />

✎ Siguiendo el estándar Pascal. Muchos compiladores aceptan, sin embargo,<br />

la omisión de “ (input, output) ”.<br />

La segunda parte, a veces llamada principal, empieza con “ begin ”ytermina<br />

con “ end. ” (punto “ . ” incluido), y entre ellos se ponen sentencias para realizar<br />

“acciones”.<br />

En ambas partes, de declaraciones y principal, las sentencias se separan<br />

mediante “ ; ”. En cualquier lugar se pueden agregar comentarios, encerrados<br />

entre “ (* ”y“*) ” que nos ayudan a entender lo que hicimos cuando volvemos<br />

amirardespués de un par de semanas.<br />

✎ También se pueden encerrar comentarios entre “ { ”y“} ”, pero no los<br />

usaremos a fin de seguir una sintaxis más parecida a otros lenguajes como<br />

“C” o Mathematica.<br />

Para evitar confusiones, normalmente se guarda el programa fuente en un<br />

archivo con el mismo nombre que en la sentencia “ program nombre ... ”, y<br />

con extensión “ .p ”o“.pas ”, para indicar que se trata de un programa Pascal.<br />

Así, generalmente guardaremos el programa fuente de nombre pepe en el archivo<br />

“ pepe.p ”o“pepe.pas”. Al compilarlo, el ejecutable cambia la extensión a<br />

“ .exe ”, de modo que obtenemos el archivo “ pepe.exe”.<br />

✎ La extensión para el ejecutable depende del sistema operativo, y puede no<br />

existir. Asimismo, puede no existir la extensión para el programa fuente.<br />

La prueba de fuego es editar, compilar y ejecutar el primer programa. Sin<br />

embargo, los detalles de cómo realizar el ciclo de edición-compilación-ejecución<br />

dependen del sistema operativo y el compilador (la marca del compilador) que<br />

estemos usando, de modo que habrá que seguir las instrucciones de los manuales<br />

o pedir auxilio a algún conocido con este primer paso.<br />

Problema 2.2 (Hola Mundo). Copiar, compilar y ejecutar el programa holamundo,<br />

guardándolo en disco o diskette como “ holamundo.pas”.<br />

✎ Muchos compiladores, entre ellos el muy difundido Turbo Pascal, hacen que<br />

al ejecutar un programa como holamundo se abra una ventana distinta que<br />

se cierra automáticamente al finalizar la ejecución del programa. El proceso


2.3. El puntapié inicial Pág. 11<br />

puede ser tan rápido que apenas nos damos cuenta de que ha sucedido algo.<br />

En estos casos es conveniente agregar un renglón con las instrucciones<br />

writeln(’ para fin’); readln<br />

al terminar el programa, antes de “ end. ” y agregando un “ ; ”alfindel<br />

renglón anterior.<br />

Turbo Pascal y otros compiladores similares no crean el ejecutable automáticamente,<br />

sino que hay que darle instrucciones para que lo hagan.<br />

Para nuestros propósitos, no será necesario crear el ejecutable.<br />

a) Observar con cuidado los signos de puntuación y qué hace cada una de las<br />

instrucciones:<br />

• El renglón inicial que comienza con “ program...”, y que termina en<br />

“ ; ”. En este programa es la única sentencia de la parte declarativa.<br />

• El comentario inmediatamente después de “ program...”, explicando al<br />

que lee el programa fuente cuál es el propósito.<br />

• El cuerpo principal que empieza con “ begin ”yterminaen“end. ”.<br />

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

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

encierra entre “ ’ ” (comillas simples). Si no tiene argumentos, “ writeln ”<br />

escribe un renglón “vacío”.<br />

b) Eliminar, repetir o cambiar las instrucciones. Por ejemplo:<br />

i) eliminar el segundo “ writeln ”,<br />

ii) y después también el tercero,<br />

iii) cambiar “ writeln ”por“WRITELN ”, y después por “ Writeln ”,<br />

iv) cambiar “ ’Hola Mundo!’ ”por“’HOLA MUNDO!’ ”,<br />

v) modificar el programa para que se escriba “ bye, bye ”envezde“y<br />

Chau! ”.<br />

c) En general, al escribir el programa usamos indentación o sangrías, i.e. espacios<br />

al comienzo de algunos de los renglones, y a veces renglones enteros<br />

en blanco, para resaltar la estructura del programa. Esto no es necesario, e<br />

inclusive podría escribirse el programa en un único renglón, y un espacio o<br />

varios no hacen diferencia:<br />

i) Eliminar o agregar espacios al comienzo, en el medio y/o al final de<br />

algunos renglones, compilar el programa y verificar que se obtiene el<br />

mismo resultado.<br />

ii) Agregar renglones en blanco o poner dos (o todos los que se quiera)<br />

renglones en uno solo, y verificar que se obtiene el mismo resultado (y<br />

recordar que “ ; ” se usa en Pascal como en castellano: para separar<br />

sentencias).<br />

✎ A los fines del programa fuente, los espacios, tabulaciones (tecla tab o<br />

similar) y renglones son intercambiables (mientras no estén encerrados<br />

entre comillas simples).<br />

d) Agregar y/o eliminar comentarios. Por ejemplo, agregar el comentario “colorin,<br />

colorado” después de writeln(’y Chau!’), en el mismo renglón y/o<br />

en el siguiente. ✄<br />

El uso de “ ; ” en Pascal resulta un poco confuso al principio, pero debe<br />

tenerse en mente que se usa como “ , ”o“; ” se usan al construir una oración


Pág. 12 El primer contacto<br />

en castellano: para separar sentencias. Del mismo modo, el “ . ”enPascaltiene<br />

el mismo sentido que el punto final en castellano. En cambio, la “ , ”seusaen<br />

forma distinta, por ejemplo, para separar argumentos de funciones como se hace<br />

en matemáticas.<br />

Pero no desesperar: por el momento no es necesario entender todo lo que se<br />

hace.<br />

2.4. Comentarios Bibliográficos<br />

El programa holamundo está tomado de [10], y es clásica su inclusión al<br />

aprender a programar.


Capítulo 3<br />

Tipos de datos elementales<br />

Recordemos que la información, incluyendo el programa ejecutable, se guarda<br />

en un lugar de memoria, como ristras de ceros y unos. Como números y<br />

caracteres se representan de esta forma, la computadora al tomar un dato debe<br />

saber si se trata de uno u otro. Esto da lugar a distintos tipos de datos, como<br />

estudiamos en este capítulo.<br />

3.1. Tipos, variables e identificadores<br />

Supongamos que guardamos las letras siguiendo el orden del abecedario, ‘ a ’<br />

como 0, ‘ b ’como1,‘c ’ como 10, y así sucesivamente. Vemos que no podríamos<br />

distinguir entre el par de letras “ ba ”ylaúnica letra “ c ”, pues ambas ser<br />

representarían como 10.<br />

Para evitar esta confusión, se decide que todas las letras ocupen siempre el<br />

mismo espacio, por ejemplo un byte de 8 bits. De esta forma tendremos (ver el<br />

problema 2.1) 2 8 = 256 posibilidades para los caracteres, lo cual es suficiente<br />

para guardar las letras de nuestro alfabeto (pero no los de algunos alfabetos<br />

orientales).<br />

Habiendo decidido esto, nos toca ahora guardar números. Como antes, es<br />

conveniente guardar a todos los números en la misma cantidad de bits. Si usáramos<br />

8 bits como hicimos para las letras, tendríamos sólo 256 números disponibles,<br />

lo cual es bien pobre. Es conveniente que las “cajas” sean más grandes.<br />

Cuando la máquina lea estas cajas que guardan letras o números, tiene que<br />

saber cuántos bits juntos tiene que leer, e interpretar la ristra de bits según sea<br />

una letra o un número. Surgen así cuatro tipos elementales de datos, cada uno<br />

con distinta codificación interna:<br />

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

• char para guardar caracteres, i.e. letras, signos de puntuación y otros que<br />

veremos más adelante,<br />

• integer para guardar números enteros, como 1, 0, −5, y<br />

• real para guardar números reales, i.e. números como 123.456.<br />

Llamamos a las variables lógicas “booleanas”, en honor a G. Boole (1815–<br />

1864), quien hizo importantes progresos al “algebrizar” la lógica.


Pág. 14 Tipos de datos elementales<br />

En la figura 3.1 ilustramos algunas cajas con datos dentro de la memoria,<br />

con sus tipos correspondientes.<br />

a 1234 123.456<br />

char integer real<br />

verdadero 4321 98.7654<br />

boolean integer real<br />

Figura 3.1: Datos de tipos elementales en la memoria.<br />

Ahora tenemos el problema de cómo acceder a esas cajas que guardan caracteres<br />

o números. Esto es sencillo: les ponemos nombres para identificarlas. Las<br />

cajas así nombradas se llaman variables pues podremos colocar en ellas datos<br />

distintos o (¡ejem!) variables y los nombres que reciben se llaman identificadores.<br />

En la figura 3.2 mostramos algunos nombres posibles para las cajas que<br />

mostramos en la figura 3.1.<br />

a 1234 123.456<br />

codigo a x<br />

verdadero 4321 98.7654<br />

fin m y<br />

Figura 3.2: Los datos con sus identificadores.<br />

Cuando redactamos el programa, debemos indicar al compilador los nombres<br />

y tipos de variables que usaremos, procedimiento llamado de declaración de<br />

variables. En el programa se pone una lista de variables y tipos después de la<br />

palabra clave “ var ”.<br />

Una cosa trae a la otra, y tenemos el problema de que no podemos poner<br />

cualquier nombre. Por ejemplo, no es conveniente usar “ program ”o“writeln ”<br />

que usa Pascal para instrucciones del lenguaje. Además, en Pascal hay nombres<br />

reservados, que no podemos usar como identificadores.<br />

✎ La lista completa de los nombres reservados está en la sección C.3,pág. 217.<br />

Aparte de estas palabras prohibidas, los identificadores en Pascal pueden ser<br />

cualquier sucesión de letras mayúsculas o minúsculas y dígitos, pero<br />

• siempre tienen que empezar con una letra,


3.1. Tipos, variables e identificadores Pág. 15<br />

• no pueden tener espacios entre medio,<br />

• ni pueden tener caracteres como $, ,+,etc.,<br />

• y por supuesto, dos variables distintas no pueden tener el mismo nombre.<br />

A diferencia de otros lenguajes (como “C” o Mathematica), según el estándar<br />

Pascal no se distingue entre mayúsculas o minúsculas tanto en los identificadores<br />

como en palabras claves. Así, podemos poner sin problemas en alguna parte<br />

“ writeln ”yenotra“WriteLn ” (como hemos hecho en el problema 2.2).<br />

✎ Sin embargo, algunos compiladores sí diferencian entre mayúsculas y minúsculas<br />

en identificadores y palabras reservadas.<br />

Problema 3.1. Decidir cuáles de los siguientes son identificadores válidos en<br />

Pascal:<br />

a) mn32xy b) 32xymn c) mn32 xy d) M32nxY e) mn 32 ✄<br />

También tenemos otro problema: cómo colocar los datos en estas cajas o<br />

variables, proceso que se llama asignación.<br />

Una forma de hacerlo es mediante “ := ” (sin espacios intermedios). Así, si<br />

queremos guardar un 2 en la variable n queesdetipoenteroponemos“n := 2”,<br />

y si queremos copiar los contenidos de la caja o variable a en la caja o variable<br />

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

pues siempre se pueden hacer asignaciones entre variables del mismo tipo, pero<br />

no es posible hacer asignaciones arbitrarias entre distintos tipos de variables, lo<br />

que veremos con algún detalle en el problema 3.11.<br />

✎ Aunque no debe dejarse espacio entremedio en “ := ”, los espacios a cada<br />

lado no son necesarios. Así, “ n:=2 ”o“n := 2” tienen el mismo efecto.<br />

✎ Otros lenguajes usan otros símbolos para la asignación, en vez de “ := ”. Al<br />

escribir en “seudo código”, muchas veces se usa el símbolo ←, que parece<br />

mucho más apropiado y evita confusiones con el símbolo =.<br />

Otra forma de colocar datos en las respectivas variables es mediante la lectura<br />

de datos, por ejemplo, si a está declarado como “ real ”, la instrucción<br />

“ readln(a)” (que veremos en la sección 3.2.1) lee el número que se ingresa por<br />

terminal y lo guarda en la variable a.<br />

Desde ya que<br />

¡nunca hay que usar el valor de una variable sin antes<br />

haber hecho una asignaciónalavariable!<br />

aunque hay compiladores que automáticamente ponen algún valor al hacer las<br />

declaraciones.<br />

Para tratar de entender esta jerigonza, pasemos a estudiar algunos ejemplos<br />

comenzando con los tipos numéricos “ integer ”y“real ”, para luego considerar<br />

los tipos “ boolean ”y“char ”.


Pág. 16 Tipos de datos elementales<br />

3.2. Tipos numéricos: entero y real<br />

La vida sería un tanto aburrida si sólo pudiéramos cambiar valores de lugar,<br />

es más interesante si podemos realizar operaciones entre estas variables. Así,<br />

suponiendo que las variables a, b y c son del tipo adecuado, una instrucción<br />

como “ a := b + c” hace que la CPU tome los datos que están en las cajas o<br />

variables b y c, las sume, y coloque el resultado en la caja o variable a.<br />

Problema 3.2. Compilar y ejecutar el programa sumardos (pág. 173) analizando<br />

la sintaxis y qué hacen las distintas instrucciones. Por ejemplo:<br />

a) ¿Cuántos comentarios tiene el programa fuente?, ¿qué pasa si se los elimina?<br />

✎ Nosotros pondremos siempre un primer comentario en el programa<br />

fuente para indicar qué hace el programa. A su vez, al ejecutar el<br />

programa trataremos de imprimir un cartel similar para que el usuario<br />

sepa qué es lo que se pretende hacer.<br />

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

nombre del programa y en la segunda se declaran a y b como de tipo entero,<br />

separando los identificadores con “ , ”.<br />

Ver qué ocurre si se cambia “ var a, b: integer;” por<br />

i) var a; b: integer;<br />

ii) var a: integer; var b: integer;<br />

iii) var a: integer; b: integer;<br />

c) ¿Quépasa si cambiamos el nombre de a por sumardos?<br />

✎ El resultado puede depender del compilador. En general no es aconsejable<br />

usar como nombre del programa una de las palabras reservadas<br />

de Pascal o de una de las variables que aparecen en el mismo programa<br />

fuente, aún cuando el compilador lo acepte.<br />

d) ¿Cuáles son las instrucciones en el cuerpo principal?<br />

e) Observar el uso de la asignación “ := ”, como en “ a := 1” donde se guarda<br />

el valor 1 en a. Cambiar el valor de a para que sea −1 y volver a ejecutar<br />

el programa.<br />

f ) “write ” escribe su/s argumento/s sin terminar un renglón, a diferencia de<br />

“ writeln ”. En cualquier caso, los argumentos (si hay más de uno) están<br />

separados por “ , ”.<br />

i) Cambiar todas las ocurrencias de “ write ”por“writeln ”, y observar<br />

los cambios en la salida del programa.<br />

ii) Cambiar los renglones<br />

write(’La suma de ’, a);<br />

write(’ y ’, b);<br />

writeln(’ es ’, a + b);<br />

por el único renglón<br />

writeln(’La suma de ’, a,’ y ’, b, ’ es ’, a + b);<br />

¿Hay alguna diferencia en la salida?<br />

g) La última línea, “ writeln; writeln(’** Fin **’) ”essólo para avisar<br />

al usuario que el programa ha terminado, y el programa ya no realizará acciones.<br />

Eliminarla y verificar el comportamiento del programa (recordando<br />

la nota al principio del problema 2.2).


3.2. Tipos numéricos: entero y real Pág. 17<br />

h) Cambiar las sentencias de modo de obtener la suma de números reales, i.e.<br />

poner “ real ”envezde“integer ”enladeclaraciónde variables. Compilar<br />

y ejecutar el programa, observando los cambios en la salida.<br />

i) Agregar una variable c, de tipo entero o real según corresponda, hacer la<br />

asignación “ c := a + b” e imprimir c en vez de a + b.<br />

j ) Modificarlo de modo de escribir también la resta (“ - ”), producto (“ * ”) y<br />

división (“ div ” para enteros y “ / ” para reales), tanto en el caso de enteros<br />

como de reales.<br />

k) ¿Quépasacuando se divide por 0? ✄<br />

Conceptualmente hay que distinguir en Pascal entre la variable, elidentificador<br />

yelvalor, i.e. entre la caja, su nombre y lo que contiene.<br />

A fin de no ser demasiado latosos, es común tanto en Pascal como en matemáticas<br />

o la vida real, no distinguir entre identificador, valor y variable, uso<br />

que nosotros seguiremos salvo cuando lleve a confusión: cuando decimos “Josées<br />

simpático”, en general queremos decir “la persona cuyo nombre es José tiene la<br />

cualidad de ser simpática”, y no que el nombre en sí essimpático, en cuyo caso<br />

diríamos “el nombre José essimpático”.<br />

3.2.1. “Readln”<br />

Es un poco tedioso estar cambiando los valores en el programa fuente para<br />

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

Problema 3.3. El programa leerdato (pág. 173) lee un entero ingresado por<br />

terminal y lo imprime. Observar la sintaxis e instrucciones, similares al programa<br />

sumardos, laúnica novedad es el uso de “ readln ”.<br />

a) Compilar y ejecutar el programa, comprobando su funcionamiento.<br />

b) Ingresar, en ejecuciones sucesivas del programa, los siguientes datos (lo que<br />

está entre “ ”, pero sin las comillas) seguidos de , conservando los<br />

espacios intermedios cuando corresponda:<br />

i) “ 23 ”; ii) “2 ”; iii) “2 3”;<br />

iv) “2a ”; v) “a2 ”; vi) “2”.<br />

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

hasta — y lo escrito no se guarda (como la instrucción en la nota<br />

al principio del problema 2.2, enlapág. 10): agregar el renglón<br />

readln;<br />

antes de<br />

write(’Entrar un ...<br />

¿Qué pasasiseingresa“” yluego“1”?,¿ysise<br />

ingresa “ 1” yluego“2”?<br />

d) Combinando los programas sumardos y leerdato, hacer un programa para<br />

ingresar por terminal dos números enteros e imprimir su suma.<br />

✎ Además de “ readln ” existe la función “ read ”, pero postergaremos su uso<br />

para más adelante. Basta decir por ahora que la instrucción “ readln(a) ”<br />

es equivalente a las instrucciones “ read(a); readln ”.


Pág. 18 Tipos de datos elementales<br />

Así como con “ writeln ”y“write ” podemos escribir más de un argumento,<br />

con “ readln ”(y“read ”) podemos leer varios argumentos a la<br />

vez, pero no usaremos esta facilidad para evitar confusiones.<br />

Los comportamientos de “ write ”y“writeln ” para leer, y de “ read ”<br />

y“readln ”sonprácticamente simétricos, aunque existen algunas diferencias<br />

importantes. Por ejemplo, en el tratamiento de los ’s como<br />

hemos visto en el inciso b.vi): “ readln(a) ” los ignora mientras no aparezca<br />

el dato a, pero“writeln(a) ”escribeunúnico renglón por vez. ✄<br />

Cuando se programa profesionalmente, es muy importante que el programa<br />

funcione aún cuando los datos ingresados sean erróneos, por ejemplo si se ingresa<br />

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

Posiblemente se dedique más tiempo a esta fase, y a la interacción con el usuario,<br />

que a hacer un programa que funcione cuando las entradas son correctas.<br />

Nosotros supondremos que el usuario siempre ingresa<br />

datos apropiados, y no haremos (salvo excepcionalmente)<br />

detección de errores. Y mucho menos<br />

nos preocuparemos por ofrecer una interfase estéticamente<br />

agradable.<br />

En cambio, trataremos de dejar claro mediante carteles<br />

qué hace el programa, qué datos han de ingresarse<br />

en cada momento y dar una señal de finalización.<br />

3.2.2. Funciones numéricas<br />

Pero volvamos a los tipos numéricos.<br />

No sólo podemos sumar o multiplicar números sino que, al igual que en las<br />

calculadoras, hay ya funciones predeterminadas como la raíz cuadrada, que en<br />

Pascal se indica por “ sqrt ”.<br />

Problema 3.4. El programa raiz (pág. 174) calcula la raíz cuadrada de un<br />

númerononegativoentradoporterminal.<br />

a) Compilar y ejecutar el programa, analizando qué hacen las distintas instrucciones.<br />

Probar el programa ingresando números reales, enteros, positivos y<br />

negativos.<br />

b) A fin de escribir en pantalla √ x, no es necesario hacer la asignación previa<br />

“ y := sqrt(x) ”:<br />

i) Reemplazar “ y ”por“sqrt(x) ”enlasentencia“writeln ”, compilar<br />

y ejecutar el programa, verificando que el resultado es el mismo.<br />

ii) Eliminar la variable y del programa por completo (eliminando inclusive<br />

su declaración).<br />

c) ¿Quépasasiseingresa un dato menor que 0?<br />

d) Muchas veces se confunde “ sqrt ”con“sqr ”, que encuentra el cuadrado de<br />

un número, y no su raíz cuadrada. Cambiar “ sqrt ”por“sqr ”yverificar<br />

el comportamiento del programa. ✄


3.2. Tipos numéricos: entero y real Pág. 19<br />

✎ Una lista de las funciones predefinidas en Pascal está enelapéndice C<br />

(pág. 215).<br />

3.2.3. La codificación de enteros y reales<br />

En Pascal, los números ocupan un número fijo de bits, por ejemplo 16 bits<br />

para el tipo “ integer ” y 32 bits para el tipo “ real ” (1) ,porloque<br />

En la máquina sólo pueden representarse un número<br />

finito de números, ya sean enteros o reales.<br />

En otras palabras, “la gran mayoría” de los números<br />

no se pueden representar exactamente en la computadora.<br />

Así, hay un máximo entero representable, que en Pascal se llama maxint,<br />

y hay un menor número real positivo representable que llamaremos εmin ydel<br />

cual hablaremos en el problema 4.17.<br />

Problema 3.5.<br />

a) Suponiendo que el tipo “ integer ” tenga asignados 16 bits, ¿cuántos números<br />

enteros pueden representarse?<br />

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

reales pueden representarse en la máquina? ✄<br />

Problema 3.6. Hacer un programa para imprimir el valor de maxint correspondiente<br />

al compilador que se usa: no declarar variables y poner el renglón<br />

writeln(’ El entero máximo es: ’, maxint)<br />

✎ En los compiladores más viejos, maxint = 32767. ✄<br />

Sea que las variables de tipo “ integer ”y“real ” ocupen el mismo lugar<br />

o no, su codificación como cadenas de bits es bien distinta. Los enteros se<br />

representan consecutivamente (del menor al mayor), mientras que para representar<br />

los números reales se dividen los bits en dos grupos, uno representando<br />

la mantisa yotroelexponente, como se hace en la notación “científica” al escribir<br />

0.123 × 10 45 (0.123 es la mantisa y 45 el exponente en base 10, pero la<br />

computadora trabaja en base 2).<br />

La representación de números reales mediante mantisa y exponente hace que<br />

—a diferencia de lo que sucede con los números enteros— la distancia entre un<br />

número real que se puede representar y el próximo vaya aumentando a medida<br />

que sus valores absolutos aumentan. Esta propiedad de densidad variable trae<br />

inconvenientes para el cálculo aproximado, como veremos en el problema 4.17<br />

y en la sección 5.1.<br />

(1) La cantidad de bits en cada caso depende del compilador. Incluso ambos tipos podrían<br />

tener asignados la misma cantidad de bits.


Pág. 20 Tipos de datos elementales<br />

1 2 4 8 16<br />

Figura 3.3: Esquema de la “densidad variable” en la codificación de números<br />

reales.<br />

✎ Para entender la “densidad variable”, puede pensarse que hay la misma<br />

cantidad de números representados entre 1 (inclusive) y 2 (exclusive) que<br />

entre2y4,oqueentre4y8,etc.Porejemplo,sihubieransólo4puntos en<br />

cada uno de estos intervalos, tendríamos un gráfico como el de la figura 3.3.<br />

Por el contrario, hay tantos números enteros representados entre 10<br />

(inclusive) y 20 (exclusive), como entre 20 y 30, etc. Es decir, entre 20 y 40<br />

hay el doble de números enteros representados que entre 10 y 20. En este<br />

caso, la “densidad es constante”.<br />

Como todo número entero es en particular un número real, es conveniente<br />

poder pasar de la representación como tipo “ integer ”alarepresentación<br />

como tipo “ real ”. Esto se hace automáticamente en Pascal: si a es de tipo<br />

“ integer ”yx es de tipo “ real ”, la asignación<br />

x := a<br />

hace que automáticamente se guarde el valor que tiene a (un entero) en x ,de<br />

modo que ahora el valor se codifica como real.<br />

✎ En particular, la cantidad de bits (y los valores de éstos) usados en la<br />

representaciones como “ integer ”o“real ” son distintas.<br />

✎ También la conversión se hace automáticamente al ingresar datos: si x es<br />

de tipo real, y tenemos la instrucción “ readln(x) ”, el ingreso de 1 como<br />

dato —un entero— hace que se guarde codificado como real en x.<br />

Problema 3.7. Compilar y ejecutar el programa enteroareal (pág. 174). Observar<br />

la declaración de variables de distinto tipo en el mismo programa. ✄<br />

Problema 3.8. Decir por qué las siguientes declaraciones de variables son incorrectas:<br />

i) var a, b, c: integer; c, d, e: real;<br />

ii) var a, b, c: integer; 2c, d, e: real;<br />

iii) var: a, b, c: integer; d, e: real;<br />

iv) var a, b, c: integer; var d, e: real; ✄<br />

Así como escribimos un entero como real, es posible que queramos pasar de<br />

un real a un entero:<br />

Problema 3.9. Modificar el programa enteroareal de modo de leer x , hacer la<br />

asignación “ a := x” e imprimir a: ¿cuál es el resultado? ✄<br />

Como puede observarse, no es posible cambiar de real a entero directamente:<br />

debe eliminarse primero la parte fraccionaria del real, lo que tradicionalmente<br />

se hace de dos formas distintas.<br />

Una es truncar el real, eliminando la parte decimal. Así, cuando truncamos:<br />

1.1 → 1, 1.9 → 1, −1.9 →−1.<br />

Otra forma es redondear el número real reemplazándolo por el entero más<br />

cercano. Claro que en el redondeo tenemos que decidir qué hacer con números


3.2. Tipos numéricos: entero y real Pág. 21<br />

como 0.5, y adoptamos la convención que se “redondea hacia arriba”: 1.1 → 1,<br />

1.9 → 2, −1.9 →−2. 0.5 → 1, −0.5 →−1.<br />

Problema 3.10. Las funciones de Pascal “ trunc ”, correspondiente a truncar,<br />

y“round ”, correspondiente a redondear, cuando aplicadas a un número real en<br />

la forma “ trunc(x)” o“round(x)” dannúmeros enteros.<br />

✎ Las definiciones de ±, pisoytechoestán en la sección D.3.<br />

a) Hacer un programa para averiguar el comportamiento de estas funciones,<br />

tomando las 8 entradas ±3, ±3.1, ±3.5, ±3.7.<br />

b) ¿Quérelaciónhay entre estas funciones y las funciones piso, ⌊x⌋, ytecho,<br />

⌈x⌉?, es decir, ¿cómo pueden escribirse unas en términos de las otras y<br />

viceversa? ✄<br />

En el próximo problema abundamos sobre las asignaciones entre variables<br />

numéricas de distinto tipo:<br />

Problema 3.11 (Asignaciones entre distintos tipos). Supongamos que<br />

hemos declarado<br />

var n: integer; x: real;<br />

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

produce un error (sugerencia: hacer un programa, compilarlo y ejecutarlo):<br />

a) x := 3.7; n := x;<br />

b) x := 3.7; n := trunc(x);<br />

c) x := 3.5; n := trunc(x);<br />

d) x := 3.7; n := trunc(-x);<br />

e) x := 3.5; n := trunc(-x);<br />

f ) x := 3.7; n := round(x);<br />

g) x := 3.5; n := round(x);<br />

h) x := 3.7; n := round(-x);<br />

i) x := 3.5; n := round(-x);<br />

j ) n := maxint div 2; n := n * (n+1) / 2;<br />

k) n := maxint div 2; n := n * (n+1) div 2;<br />

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

También es posible hacer operaciones mezclando variables numéricas de distintos<br />

tipos:<br />

Problema 3.12. Hacer un programa donde se declara a de tipo entero y b de<br />

tipo real, se lean a y b, y se escriban en pantalla los resultados de: a + b, a ∗ b,<br />

a/b, b/a. ¿Dequétiposon los resultados en cada caso? Sugerencia: modificar<br />

la declaración de variables en el programa sumardos. ✄<br />

De aquí enmás supondremos que el lector compilará<br />

yejecutará cada programa mencionado en los<br />

problemas, probándolo con distintos datos. En lo sucesivo,<br />

generalmente omitiremos esta instrucción.


Pág. 22 Tipos de datos elementales<br />

Problema 3.13. El programa segundos (pág. 175) permite pasar de segundos<br />

a horas, minutos y segundos, usando la función “ mod ”: “ a mod b ”daesencialmente<br />

el resto de la división de a por b.<br />

✎ El número de segundos a ingresar no debe superar maxint.<br />

a) Agregarle instrucciones de modo de poder verificar si el resultado es correcto,<br />

pasando de horas, minutos y segundos a segundos.<br />

b) ¿Quépasa si se coloca el renglón “ writeln(hs, ’ hs, ’,... ”inmediatamente<br />

después del renglón “ writeln(segs, ’ segundos...” yantesde<br />

“ mins := segs div ... ”? ✄<br />

Problema 3.14.<br />

a) Hacer un programa para averiguar el comportamiento de “ mod ”, tomando<br />

como entradas a = ±7 yb = ±3, calculando “ a mod b ”.<br />

b) Comprobar si el valor de “ (a div b) * b + (a mod b) ” coincide con el<br />

de a. ✄<br />

3.3. Variables lógicas<br />

Estamos acostumbrados a valores numéricos, y no es sorpresa que por ejemplo<br />

las variables del tipo “ integer ” o entero, admitan valores como 1 o −987.<br />

Sin embargo, cuesta acostumbrarse a las variables de tipo lógico o “ boolean ”,<br />

que sólo admiten valores verdadero o “ true ”, y falso o “ false ”. Antes de<br />

trabajar con estas variables en programación, practiquemos un poco:<br />

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

i) 1=2. ii) 1> 2.<br />

iii) 1≤ 2. iv) 4× 5 > 3<br />

4 .<br />

v) 1< 2 < 3. vi) 1< 2 < 0.<br />

vii) 1< 2o2< 0. viii) 1 = cos 1.<br />

ix ) {a, b, c} = {b, a, c}. x ) {0, 1, 2, 3}⊂N. ✄<br />

Habiendo estirado un poco los músculos, tiene sentido preguntarle a la computadora<br />

si un número es positivo o no, y que guarde el resultado en una variable<br />

lógica:<br />

Problema 3.16. Compilar y ejecutar el programa positivo (pág. 175), analizando<br />

qué hacen las distintas instrucciones.<br />

Agregar una variable pos, declarada como lógica (“ boolean ”), incluir en el<br />

cuerpo del programa la asignación “ pos := a > 0 ” e imprimir pos,envezde<br />

“ a > 0”. ✄<br />

Debemos destacar que al comparar números, Pascal usa a veces una sintaxis<br />

ligeramente diferente a la usual:<br />

Matemáticas Pascal<br />

= =<br />

= <br />

> ><br />

≥ >=<br />

< <<br />


3.4. Caracteres Pág. 23<br />

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

que pregunta si los valores a ambos lados coinciden:<br />

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

resultado en a,<br />

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

no se modifica el valor de a.<br />

Así como para números tenemos la suma, el producto o el inverso aditivo,<br />

para variables lógicas tenemos la conjunción y, a veces simbolizada con ∧, la<br />

disyunción o, simbolizada con ∨, y la negación no, simbolizada con ¬. EnPascal<br />

usamos respectivamente “ and ”, “ or ”y“not ”.<br />

Por ejemplo si a y b son de tipo entero, pondríamos<br />

Matemáticas Pascal<br />

(a >0) ∧ (b >0) (a > 0) and (b > 0)<br />

(a >0) ∨ (b >0) (a > 0) or (b > 0)<br />

¬(a >0) not (a > 0)<br />

pero la expresión matemática a


Pág. 24 Tipos de datos elementales<br />

En Pascal, dado un carácter podemos encontrar su número de orden mediante<br />

“ ord ”yrecíprocamente, dado un entero podemos ver qué carácter le<br />

corresponde mediante la función “ chr ” (2) .Elestándar Pascal establece que:<br />

• Los caracteres ‘ 0 ’,‘ 1 ’,. . . ,‘ 9 ’están numéricamente ordenados y son consecutivos,<br />

pero<br />

• las letras minúsculas, ‘ a ’,‘ b ’,...,‘z ’, si bien están ordenadas, ¡no son<br />

necesariamente consecutivas!<br />

• y lo mismo para las letras mayúsculas.<br />

Afortunadamente, el código ASCII —con el que trabajaremos— satisface<br />

estos requisitos, y más aún, las letras minúsculas ‘ a ’,...,‘z ’ son consecutivas y<br />

lo mismo para las mayúsculas.<br />

Desafortunadamente, si bien para nosotros ‘ ~n ’estáentre‘n ’y‘o ’, esto no es<br />

asíenelcódigo ASCII, pues la letra ‘ ~n ’nisiquieraestácodificada.Niquéhablar<br />

de ch o ll, que se consideran (en cada caso) como dos caracteres distintos. Todo<br />

esto puede traer problemas al clasificar u ordenar alfabéticamente.<br />

Problema 3.18 (Ordinales y caracteres). El programa caracteres1 (pág.<br />

175) toma un carácter como entrada, retorna su número de orden y verifica la<br />

corrección.<br />

a) ¿Quépasa si cambiamos el nombre del programa de “ caracteres1” a<br />

“ char ”?, ¿ya “char1 ”?<br />

✎ Recordar lo dicho sobre identificadores al principio del capítulo y en<br />

el problema 3.2.c).<br />

b) ¿Quépasasiseescribecomoentrada“tonto ”?<br />

c) ¿Cuál es el ordinal correspondiente a los caracteres ‘ a ’, ‘ A ’, ‘ ’ (espacio),<br />

y tabulación (tecla “tab”)?<br />

d) Hacer un programa que, inversamente, dado un número retorne el carácter<br />

correspondiente.<br />

e) Averiguar si hay caracteres correspondientes a números mayores que 127 (el<br />

resultado dependerá delamáquina). ¿Y a números mayores que 255?<br />

f ) ¿Cuál es el carácter correspondiente al número 7 (en ASCII)? ✄<br />

3.5. Comentarios Bibliográficos<br />

Los problemas 3.1 y 3.8 están basados en similares de [3].<br />

(2) ¡No confundir “ char ”con“chr ”!


Capítulo 4<br />

Tomando control<br />

Con las operaciones que hemos visto no podemos hacer mucho más que lo<br />

que hace una calculadora sencilla. Las cosas empiezan a ponerse interesantes<br />

cuando disponemos de estructuras de control de flujo, esto es, instrucciones que<br />

nos permiten tomar decisiones sobre si realizar o no determinadas instrucciones<br />

o realizarlas repetidas veces.<br />

Al disponer de estructuras de control, podremos verdaderamente comenzar a<br />

describir algoritmos, es decir, instrucciones (no necesariamente en un lenguaje de<br />

programación) que nos permiten llegar a determinado resultado, y apuntar hacia<br />

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

a un lenguaje de programación.<br />

✎ Se conocen pocos detalles de la vida de Abu Ja’far Muhammad ibn Musa<br />

al-Khwarizmi’. Presuntamente nació alrededor de 780 en Bagdad (Irak)<br />

—cuando Harun al-Rashid, el califa de “Las mil y una noches”, comenzaba<br />

su califato— y murió en 850.<br />

Al-Mamun hijo y sucesor de al-Rashid, continuó la tradición de su<br />

padre de patrocinar las ciencias y fundó laacademia“Casadelsaber”,a<br />

la que se incorporó Al-Khwarizmi.<br />

Al-Khwarizmi escribió y tradujo varios textos científicos (de matemática,<br />

geografía, etc.) del griego al árabe. El más importante de ellos es “Hisab<br />

al-jabr w’al-muqabala”, de donde surge la palabra “álgebra” conque ahora<br />

designamos a esa rama de la matemática.<br />

Al-Khwarizmi también escribió un tratado sobre números indo-arábigos<br />

que se ha perdido, pero se conservó una traducción al latín llamada<br />

“Algoritmi de numero Indorum”, para indicar su autor, que dio lugar a la<br />

palabra “algoritmo” que usamos.<br />

Los programas irán aumentando en longitud, lo que nos obligará a encarar<br />

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

qué queremos hacer, luego en cómo lo vamos a hacer, y finalmente pasar a los<br />

detalles. Pascal, y casi todos los lenguajes de programación, son poco “naturales”<br />

en este sentido, exigiendo —por ejemplo— la declaración de variables al<br />

principio del programa fuente, mientras que nosotros pensaremos el programa<br />

de “abajo hacia arriba” (o casi), y recién al final miraremos cómo declarar las<br />

variables necesarias.<br />

En Pascal, las estructuras fundamentales de control son “ if ”, “ while ”,<br />

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


Pág. 26 Tomando control<br />

✎ En Pascal hay otras estructuras de control, como “ case ”, que no veremos.<br />

En otros lenguajes de programación existen otras estructuras, pero casi<br />

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

simular las otras.<br />

4.1. “If”<br />

Supongamos que al cocinar decidimos bajar el fuego si el agua hierve, es decir<br />

realizar cierta acción si se cumplen ciertos requisitos. Podríamos esquematizar<br />

esta decisión con la sentencia:<br />

si el agua hierve entonces bajar el fuego.<br />

A veces queremos realizar una acción si se cumplen ciertos requisitos y<br />

además realizar una acción alternativa si no se cumplen. Por ejemplo, si para<br />

ir al trabajo podemos tomar el colectivo o un taxi —que es más rápido pero<br />

más caro que el colectivo— dependiendo del tiempo que tengamos decidiríamos<br />

tomar uno u otro, que podríamos esquematizar como:<br />

si es temprano entonces tomar el colectivo en otro caso tomar el taxi.<br />

En Pascal podemos tomar este tipo de decisiones, usando la construcción<br />

“ if...then...” para el esquema “si ...entonces ...”, mientras que para la<br />

variante “si ...entonces ...en otro caso ...” usamos“if...then...else...”<br />

Por supuesto, entre “ if ”y“then ” tenemos que poner una condición (una<br />

expresión lógica) que pueda evaluarse como verdadera o falsa.<br />

Problema 4.1.<br />

a) Agregar el siguiente renglón al programa leerdato (pág. 173):<br />

if (a > 10) then writeln(chr(7));<br />

inmediatamente después del renglón “ writeln(’El entero...);”.<br />

¿Cuál es el efecto de agregar este renglón?<br />

b) Modificar el programa leerdato de modo que la computadora emita un sonido<br />

cuando el número entrado es mayor que 0 y menor o igual a 10 (y no haga<br />

nada en otro caso). ✄<br />

Problema 4.2. El valor absoluto para x ∈ R se define como<br />

<br />

x si x ≥ 0,<br />

|x| =<br />

−x en otro caso.<br />

El programa valorabsoluto (pág. 176) realiza este cálculo. Observar en especial<br />

la estructura “ if...then...else...”. ¿Qué pasa si se eliminan los<br />

paréntesis después del “ if ”?,¿ysisecambia“>= ”por“> ” ?, ¿y si se agrega<br />

“ ; ”antesde“else ”?<br />

La función “ abs ” de Pascal hace el cálculo del valor absoluto. Incorporarla<br />

al programa y verificar que se obtienen los mismos resultados. ✄<br />

Problema 4.3. Hacer un programa que, dado un número real, encuentre su<br />

piso y su techo. Sugerencia: recordar el problema 3.10. ✄


4.1. “If” Pág. 27<br />

A veces es conveniente encadenar varios “ if ”:<br />

Problema 4.4. El programa comparar (pág. 176) toma como datos los números<br />

enteros a y b y determina cuál es mayor o si son iguales.<br />

a) ¿Quépasa si se escriben las líneas que empiezan con “ if ”yterminanen<br />

“ ; ”enunsolorenglón?<br />

b) ¿Quépasasiseincluyen“; ” al final de cada uno de los renglones originales?<br />

c) Modificar el programa para que siempre escriba en la salida el número a<br />

primero (i.e. si a = −5 yb = 3 escriba “ -5 es menor que 3 ”envezde“3<br />

es mayor que -5 ”).<br />

d) Modificarloparaquesólo decida si a = b o a = b.<br />

e) Modificarlo para que sólo decida si a>bo a ≤ b. ✄<br />

Problema 4.5 (Comparación de caracteres). El programa caracteres2 (en<br />

la pág. 176) decide si un carácter entrado por terminal es una letra minúscula<br />

o una mayúscula o no es una letra, mediante comparación.<br />

Modificarlo de modo de hacer un programa para clasificar un carácter ingresado<br />

como uno de los siguientes:<br />

a) una letra minúscula vocal, b) una letra minúscula consonante,<br />

c) una letra mayúscula vocal, d) una letra mayúscula consonante,<br />

e) undígito (0, 1,...,9), f )niletranidígito.<br />

✎ Recordando lo dicho al principio de la sección 3.4, no deben ingresarse<br />

vocales acentuadas o “ ~n ”. ✄<br />

Problema 4.6. El Gobierno ha decidido establecer impuestos a las ganancias<br />

en forma escalonada: los ciudadanos con ingresos hasta $10000 no pagarán<br />

impuestos; aquéllos con ingresos superiores a $10000 pero que no sobrepasen<br />

$30000, deberán pagar 10 % de impuestos; aquéllos cuyos ingresos sobrepasen<br />

$30000 pero no sean superiores a $50000 deberán pagar 20 % de impuestos, y<br />

los que tengan ingresos superiores a $50000 deberán pagar 35 % de impuestos.<br />

a) Hacer un programa para calcular el impuesto dado el monto de la ganancia.<br />

¿Qué tipos de datos habrá que usar?<br />

b) Modificarlo para determinar también la ganancia neta (una vez deducidos<br />

los impuestos) del ciudadano.<br />

c) Modificarlo de modo que los impuestos y ganancia neta se impriman hasta<br />

el centavo (y no más). ✄<br />

Problema 4.7 (Años bisiestos). Desarrollar un programa para decidir si un<br />

año dado es o no bisiesto.<br />

Julio César (alrededor de 100–44 a. de C.) cambió el calendario egipcio, que<br />

estaba basado en un año de exactamente 365 días, a un nuevo calendario, el juliano,<br />

conañosbisiestos cada 4 años aproximando mejor la verdadera longitud<br />

del año. Sin embargo, cálculos posteriores mostraron que la longitud del año<br />

es aproximadamente 365.2422 días. Con el paso de los siglos, las diferencias<br />

anuales de 0.0078 días en promedio se fueron acumulando, y hacia el año 1582<br />

el Papa Gregorio comenzó un nuevo calendario (el gregoriano). Por un lado,<br />

se agregaron 10 días a la fecha, de modo que el 5 de octubre de 1582 pasó aser<br />

el 15 de octubre (y nunca existieron los días entre el 6 y el 14 de octubre de<br />

ese año). Por otro lado, se decidió quelosañosbisiestos serían precisamente


Pág. 28 Tomando control<br />

los divisibles por 4, excepto que aquéllos que fueran divisibles por 100 serían<br />

bisiestos sólo cuando fueran divisibles por 400 (así elaño 1700 no es bisiesto<br />

pero sí el 2000). Con esta convención, el año promedio tiene 365.2425 días. No<br />

todos los países del mundo adoptaron este calendario inmediatamente. En Gran<br />

Bretaña y lo que es ahora Estados Unidos de Norteamérica, se adoptó recién<br />

en 1752, por lo que se debieron agregar 11 días más; Japón cambió en 1873,<br />

Rusia en 1917 y Grecia en 1923. Aún hoy algunas iglesias ortodoxas mantienen<br />

el calendario juliano, especialmente para determinar la fecha de las Pascuas.<br />

✎ A veces con un pequeño esfuerzo podemos hacer el cálculo más eficiente.<br />

Un esquema para resolver el problema anterior, si anio es el año ingresado,<br />

es<br />

write(anio);<br />

if (anio mod 400 = 0) then writeln(’ es bisiesto’)<br />

else if (anio mod 100 = 0) then writeln(’ no es bisiesto’)<br />

else if (anio mod 4 = 0) then writeln(’ es bisiesto’)<br />

else writeln(’ no es bisiesto’)<br />

Sin embargo, el esquema<br />

write(anio);<br />

if (anio mod 4 0) then writeln(’ no es bisiesto’)<br />

else if (anio mod 100 0) then writeln(’ es bisiesto’)<br />

else if (anio mod 400 0) then writeln(’ no es bisiesto’)<br />

else writeln(’ es bisiesto’)<br />

es más eficiente, pues siendo que la mayoría de los números no son múltiplos<br />

de 4, en la mayoría de los casos haremos sólo una pregunta con el segundo<br />

esquema pero tres con el primero.<br />

Nosotros no vamos a preocuparnos por la eficiencia del programa, pero<br />

haremos comentarios (¡como éste!) de tanto en tanto. ✄<br />

Problema 4.8. Desarrollar un programa que, tomando como entrada un número<br />

natural entre 1 y 3000, lo escriba en romano (e.g. entrando 2999 se debe<br />

obtener MMCMXCIX). Sugerencia: primero hacer un programa para tratar el<br />

caso entre 1 y 30, y después ir agregando posibilidades.<br />

✎ Las letras usadas para números romanos son I, V, X, L, C, D, M, correspondientes<br />

respectivamente a 1, 5, 10, 50, 100, 500, 1000.<br />

✎ El problema es ciertamente tedioso para escribir, pero es típico de muchas<br />

aplicaciones, en las que no hay atajos. ✄<br />

A veces es conveniente y aún necesario agrupar instrucciones, lo que en<br />

Pascal se hace mediante “ begin...end”, como en la parte principal de todo<br />

programa.<br />

✎ La expresión “ end. ” (con punto) sólo debe aparecer al final del programa<br />

fuente.<br />

Este “agrupar” es similar al uso de paréntesis en expresiones matemáticas,<br />

“ ( ” correspondiendo a “ begin ”y“) ” correspondiendo a “ end ”.<br />

Como “ begin...end” es similar a los paréntesis, “ begin ”y“end ”noson<br />

sentencias en sí, por lo tanto<br />

Es incorrecto poner un “ ; ” inmediatamente antes<br />

de un “ end ”.


4.1. “If” Pág. 29<br />

Veamos un ejemplo donde podríamos usar “ begin...end”:<br />

Problema 4.9. Sea f : R → R la función definida como<br />

<br />

x<br />

f(x) =<br />

2 si x>0,<br />

0 si x ≤ 0.<br />

a) Hacer un esquema del gráfico de la función.<br />

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

c) Supongamos que queremos imprimir además un cartel diciendo si el valor<br />

entrado es positivo (y por lo tanto f(x) =x2 ) o si no lo es (y por lo tanto<br />

f(x) = 0). Una posibilidad es, habiendo declarado a x y y como de tipo<br />

“ real ”, poner dos “ if ”’s consecutivos, por ejemplo:<br />

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

else writeln( x, ’ no es positivo’);<br />

if (x > 0) then y := x*x else y := 0;<br />

writeln(’El valor de la funcion es ’, y);<br />

Incluir en el programa del inciso anterior estas modificaciones.<br />

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

agrupando las instrucciones en cada caso:<br />

if (x > 0) then begin<br />

writeln( x, ’ es positivo’);<br />

y := x*x<br />

end<br />

else begin<br />

writeln( x, ’ no es positivo’);<br />

y := 0<br />

end;<br />

writeln(’El valor de la funcion es ’, y);<br />

Modificar el programa del inciso b) incorporando esta variante. ¿Quépasa<br />

si se elimina el “ end ”antesdel“else ”?,¿ysisepone“; ”entre“end ”<br />

y“else ”? ✄<br />

Otras veces el uso de “ begin...end” evita confusiones:<br />

Problema 4.10. Supongamos que hemos declarado<br />

var a, b, n, z: integer;<br />

y que tenemos el renglón<br />

if (n > 0) then if (a > b) then z := a else z := b;<br />

a) ¿Quétiene de confuso este renglón?<br />

b) Decir cuál es el valor de z inmediatamente después de ejecutar el renglón<br />

original cuando los valores de a, b, n y z son:<br />

i) a =1,b =2,n =1,z =0;<br />

ii) a =1,b =2,n = −1, z =0;<br />

iii) a =2,b =1,n =1,z =0;<br />

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


Pág. 30 Tomando control<br />

c) Decir si el renglón es equivalente a:<br />

i) if (n > 0) then begin<br />

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

ii) if (n > 0) then begin<br />

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

4.2. “While”<br />

La estructura “ while ” es una estructura para realizar repetidas veces una<br />

misma tarea. Junto con “ repeat ”y“for ” —que veremos más adelante— reciben<br />

el nombre común de lazos o bucles para indicar esta capacidad de repetición.<br />

Supongamos que voy al supermercado con cierto dinero para comprar la<br />

mayor cantidad posible de botellas de cerveza. Podría ir calculando el dinero<br />

que me va quedando a medida que pongo botellas en el carrito: cuando no<br />

alcance para más botellas, iré a la caja. Una forma de poner esquemáticamente<br />

esta acción es<br />

mientras me alcanza el dinero, poner botellas en el carrito.<br />

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

donde “ do ” reemplaza a la “ , ” en la sentencia anterior, y entre “ while ”y<br />

“ do ” debe ponerse una expresión lógica.<br />

Observamos desde ya que:<br />

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

dinero inicial no me alcanza, no pongo ninguna botella en el carrito.<br />

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

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

que nunca termina.<br />

Por ejemplo, si tomamos un número positivo y le sumamos 1, al resultado<br />

le sumamos 1, y así sucesivamente mientras los resultados sean positivos.<br />

O si tomamos un número positivo y lo dividimos por 2, luego otra vez por<br />

2, etc. mientras el resultado sea positivo.<br />

✎ Al menos en teoría. Como veremos en los problemas 4.15 y 4.17, la<br />

máquina tiene un comportamiento “propio”.<br />

Por cierto, en el ejemplo de las botellas en el supermercado podríamos realizar<br />

directamente el cociente entre el dinero disponible y el precio de cada botella,<br />

en vez de realizar el lazo mientras. Esloquevemosenelpróximo problema:<br />

Problema 4.11. El programa resto (pág. 177) calcula el resto de la división de<br />

a ∈ N por b ∈ N mediante restas sucesivas.<br />

a) Antes de compilar y ejecutar el programa, hacemos una “prueba de escritorio”.<br />

Por ejemplo, si ingresamos a =10yb =3,podríamos poner


4.2. “While” Pág. 31<br />

Paso acción a b r<br />

0 (antes de empezar) sin valor sin valor sin valor<br />

1 leer a 10<br />

2 leer b 3<br />

3 r := a 10<br />

4 r ≥ b: verdadero<br />

5 r := r - b 7<br />

6 r ≥ b: verdadero<br />

7 r := r - b 4<br />

8 r ≥ b: verdadero<br />

9 r := r - b 1<br />

10 r ≥ b: falso<br />

11 imprimir r<br />

donde indicamos los pasos sucesivos que se van realizando y los valores de<br />

las variables a, b y r (el último valor que aparece es el que tiene antes de<br />

ejecutarse el paso correspondiente). Podemos comprobar entonces que los<br />

valores de a y b al terminar el programa son los valores originales, mientras<br />

que r se modifica varias veces.<br />

✎ El lector puede la “prueba de escritorio” como le parezca más clara.<br />

La presentada es sólo una posibilidad.<br />

Las “pruebas de escritorio” sirven para entender el comportamiento<br />

del programa y detectar algunos errores (pero no todos). Son útiles<br />

al comenzar a programar, en programas ni demasiado sencillos, como<br />

los que hemos visto hasta ahora, ni demasiado complicados como<br />

varios de los que veremos en los capítulos siguientes.<br />

Otra forma —bastante más primitiva— de entender el comportamiento<br />

de un programa y eventualmente encontrar errores, es probarlo<br />

con distintas entradas, como hemos hecho desde el comienzo, por ejemplo<br />

en el problema 2.2 yprácticamente en todo el capítulo 3.<br />

Alentamos al lector para que haga las “pruebas de escritorio” de<br />

los restantes problemas de éste y el siguiente capítulos.<br />

b) Hacer una “prueba de escritorio” con otros valores de a y b, por ejemplo<br />

con 0


Pág. 32 Tomando control<br />

valor obtenido en el inciso f )?, ¿podrías explicar por qué?<br />

h) ¿Hay algún ejemplo donde tenga sentido declarar las variables a, b y r como<br />

de tipo real en vez de entero? Sugerencia: pensarenradianesyb =2π. ✄<br />

Problema 4.12. El programa tablaseno (pág. 177) produce una tabla del seno<br />

para ángulos expresados en grados. Tanto el ángulo inicial, el ángulo final como<br />

el incremento, son entrados por el usuario.<br />

a) Observar el uso de “ const ”—porconstante— para dar un valor aproximado<br />

de π, anterior a la declaración de variables. Observar también que no<br />

se hace una asignación a pi, sino que se usa el signo “ = ”. El uso de const<br />

hace que no se puedan hacer asignaciones a pi pues no es una variable (es<br />

una constante): tratar de hacer una asignación a pi yobservarquépasa. b) Antes o después de ejecutar el programa, verificar que se obtienen los mismos<br />

resultados de una “prueba de escritorio” (como en el problema 4.11, ahora<br />

con las variables inicial, final, incremento, grados y radianes).<br />

c) Algunos compiladores Pascal tienen pre-definida la constante pi, peronoes<br />

estándar. Verificar si el compilador usado está en esta categoría comentando<br />

el renglón donde se define pi, i.e. encerrándo el renglón entre “ (* ”y“*) ”.<br />

d) En vez de poner manualmente un valor aproximado de π, escomún usar la<br />

igualdad π =4× arctan 1.<br />

i) Cambiar el valor “ 3.14159265” dadoapi en el programa original,<br />

reemplazándolo por “ 4 * arctan(1)”. En general los compiladores<br />

no aceptan este cambio, pues el estándar Pascal no lo permite.<br />

ii) Eliminar el renglón donde se define a pi como una constante y definir<br />

en cambio pi como una variable de tipo real, y en el cuerpo del programa<br />

hacer la asignación de la igualdad anterior. Comparar con los<br />

resultados obtenidos originalmente.<br />

✎ Podría usarse otro valor similar para π, como2×arccos 0 o 2×arcsen 1,<br />

pero arctan (el arco tangente) es la única función trigonométrica inversa<br />

definida en el estándar Pascal.<br />

e) ¿Qué pasa si el valor de incremento es cero? ¿Y si el valor de incremento<br />

es positivo pero el de final es menor que el de inicial?<br />

f ) Modificar el programa tablaseno para producir en cambio una tabla de logaritmos<br />

en base 10, donde los valores inicial , final e incremento son de tipo<br />

real. Sugerencia: enPascalestá definido ln x =log e x, yparacualquiera, b<br />

y x razonables, log a x =log b x/ log b a. ✄<br />

Otro uso muy común de los lazos (“ while ”, “ repeat ”o“for ”) es el cálculo<br />

de sumas o productos de muchos términos. En forma similar al contador k del<br />

problema 4.11.f ), si el resultado se va a guardar en la variable resultado, ésta se<br />

inicializaa0enelcasodelasumaoa1enelcasodelproductoantes del lazo,<br />

yenéste se van sumando o multiplicando los términos deseados modificando<br />

resultado. Ilustramos esta técnica en el siguiente problema:<br />

Problema 4.13 (Suma de Gauss). Para n ∈ N consideremos la suma<br />

sn =1+2+3+···+ n =<br />

n<br />

i,<br />

i=1


4.2. “While” Pág. 33<br />

que, según la fórmula de Gauss, es<br />

sn =<br />

n × (n +1)<br />

.<br />

2<br />

Según se dice, Karl Friederich Gauss (1777–1855) tenía8años cuando el<br />

maestro de la escuela le dio como tarea sumar los números de 1 a 100 para<br />

mantenerlo ocupado (y que no molestara). Sin embargo, hizo la suma muy<br />

rápidamente al observar que la suma era 50 × 101.<br />

Las contribuciones de Gauss van mucho más allá deestaanécdota, sus<br />

trabajos son tan profundos y en tantas ramas de las matemáticas que le valieron<br />

el apodo de “príncipe de los matemáticos”. Para algunos, fue el más grande<br />

matemático de todos los tiempos.<br />

El programa gauss (pág. 178) calculasnsumando los términos (y no usando<br />

la fórmula de Gauss).<br />

a) ¿Cuál es el valor de n al final del lazo? Sugerencia: hacer una “prueba de<br />

escritorio”.<br />

b) ¿Quépasasiseingresan≤0? c) Lavariablesuma está declarada como de tipo real. Si se declarara como de<br />

tipo entero, ¿cuál sería el máximo n para el cual se podría calcular sn (i.e.<br />

para el cual sn ≤ maxint)?<br />

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

e) Modificar el programa de modo que a la salida exprese también el valor<br />

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

100).<br />

f )Modificarellazo“while ” para que se sume “al derecho”, i.e. primero los<br />

términos más chicos. ✄<br />

Problema 4.14.<br />

a) Modificar el programa gauss para obtener, dado n ∈ N, sufactorial, denotado<br />

por n! y definido como<br />

n! =1× 2 ×···×n.<br />

Sugerencia: recordar lo dicho antes del problema 4.13 sobre el cálculo de<br />

sumasyproductosdevariostérminos.<br />

b) ¿Hay alguna diferencia entre los resultados multiplicando “al derecho” (de<br />

menor a mayor) y “al revés” (de mayor a menor)?<br />

✎ Es posible que sí cuando n es grande, debido a errores numéricos.<br />

c) Determinar cuántos ceros hay al final de la expresión decimal de 30!, y comparar<br />

con la cantidad de ceros que aparecen en el resultado del programa.<br />

Sugerencia: contar las veces que 2 y 5 son factores de 30!.<br />

✎ 30! tiene 33 cifras decimales. ✄<br />

Problema 4.15. Recordando que la suma y producto de enteros son enteros,<br />

nos preguntamos si el siguiente programa termina o no:<br />

program terminaono(input, output);<br />

var i: integer;<br />

begin


Pág. 34 Tomando control<br />

i := 1;<br />

while (i * i


4.4. “For” Pág. 35<br />

b) ¿Quépasasisecalculaεmaq mediante<br />

eps := 1;<br />

repeat eps := eps/2 until (1 + eps = 1);<br />

eps := 2 * eps;<br />

writeln(’epsmaq es: ’, eps); ?<br />

✎ Es posible que los valores sean distintos, debido a la forma de trabajo<br />

internadelamáquina.<br />

c) Cambiar el lazo “ repeat ”enelcálculo de εmin porunlazo“while ”haciendo<br />

las modificaciones necesarias. ¿Cuál es más natural, “repeat ”o<br />

“ while ”? ✄<br />

Para calcular εmaq y εmin hemos violado justamente la regla de oro:<br />

4.4. “For”<br />

¡Nunca deben compararse números reales por igualdad<br />

sino por diferencias suficientemente pequeñas!<br />

Hay veces que queremos repetir una acción un número fijo de veces. Por<br />

ejemplo, al subir una escalera con 10 escalones haríamos algo como<br />

hacer 10 veces: subir un escalón.<br />

En Pascal no existe una estructura que traduzca esta acción directamente.<br />

✎ Pero sí en Modula, el sucesor de Pascal también desarrollado por N. Wirth.<br />

En cambio, Pascal cuenta con una estructura un poco más flexible que nos<br />

permite imitarla: si contamos los escalones con el “contador” e, quevariaráentre<br />

1 y 10, podríamos poner el esquema<br />

para e desde1a10hacer subir el e-ésimo escalón.<br />

Este esquema se reproduce en Pascal (habiendo declarando e como de tipo<br />

entero) con la estructura<br />

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

que ciertamente podría cambiarse por un lazo “ while ”:<br />

e := 1;<br />

while (e


Pág. 36 Tomando control<br />

Así, una de las diferencias entre “ while ”(o“repeat ”) y “ for ”esque<br />

no aparece explícitamente una condición lógica. Otra de las diferencias es que,<br />

según el estándar Pascal, el valor de la variable que indica el paso, llamada<br />

variable de control (en el ejemplo e) no está definido al finalizar el lazo “ for ”,<br />

aunque muchos compiladores no respetan esta convención.<br />

En general, usaremos “ for ” cuando sabemos exactamente la cantidad de<br />

veces que hay que pasar por el lazo, por ejemplo para calcular la potencia x n<br />

cuando x ∈ R y n ∈ N, donde debemos multiplicar x por sí mismon veces:<br />

Problema 4.18. El programa potencia (pág. 180) calcula la potencia n-ésima<br />

del real x.<br />

a) Compilarlo y ejecutarlo, verificando su comportamiento.<br />

b) No es posible tomar bases y exponentes muy grandes sin que haya overflow<br />

o underflow, i.e. resultados que no se pueden representar en la máquina por<br />

ser muy grandes o muy pequeños. Probar con distintos valores hasta obtener<br />

este comportamiento, e.g. 10 10 , 100 100 ,(1/10) 10 ,(1/100) 100 ,etc.<br />

c) Modificar el programa de modo de imprimir el valor de i al finalizar el lazo.<br />

✎ Como mencionáramos, según el estándar el valor no está definido, pero<br />

muchos compiladores no respetan esta convención y el valor obtenido<br />

puede depender del compilador: no debe usarse esta variable después<br />

del lazo “ for ” sin antes re-asignarla.<br />

d) Agregar sentencias para considerar también el caso de n negativo. Sugerencia:<br />

a b =1/a −b , usar la función “ abs ” y/o condicional “ if ”.<br />

e) Una variante de “ for...to...do...”, que va incrementando la variable<br />

de control usada de a 1, es usar “ for...downto...do...”, que va disminuyéndola<br />

en 1 en cada paso.<br />

Cambiar el renglón<br />

for i := 1 to n do pot := pot * x;<br />

por el siguiente:<br />

for i := n downto 1 do pot := pot * x;<br />

y verificar que el comportamiento es el mismo. Observar que con “ downto ”<br />

el valor inicial debe ser mayor o igual que el valor final para que se realice<br />

algún paso.<br />

f ) Algunos compiladores admiten la operación “ x ** y”, que no es del estándar<br />

Pascal, para el cálculo de la potencia xy (x ∈ R+, y ∈ R). Verificar si<br />

el compilador usado admite esta sentencia y, en caso afirmativo, comparar<br />

con el resultado anterior cuando y ∈ N.<br />

g) Otra forma de calcular la potencia en general es usar x y = e y×ln x .Incorporar<br />

esta fórmula al programa para comparar con los resultados anteriores.<br />

✎ Es posible que difieran debido a errores numéricos. ✄<br />

Muchos de los problemas que hemos visto usando “ while ”o“repeat ”<br />

pueden hacerse con “ for ” con modificaciones sencillas, observando que para<br />

considerar valores descendientes con “ for ”, se debe usar “ downto ”envezde<br />

“ to ”. Por ejemplo, en el programa gauss (pág. 178) ellazo<br />

while (n > 0) do begin suma := suma + n; n := n-1 end;


4.5. Ingresando muchos datos: “Eoln” Pág. 37<br />

podría reemplazarse por el lazo<br />

for i := n downto 1 do suma := suma + i;<br />

donde la variable i es de tipo entero.<br />

Sin embargo, no siempre se puede o es sencillo usar “ for ”:<br />

Problema 4.19.<br />

a) Modificar los programas tablaseno (pág. 177) ygauss (pág. 178) cambiando<br />

el lazo “ while ” por uno “ for ”.<br />

b) ¿Podría cambiarse el lazo “ while ” por uno “ for ” en el programa resto<br />

(problema 4.11)?, ¿y el lazo “ repeat ” por uno “ for ” en el programa<br />

epsmin (problema 4.17)? ✄<br />

4.5. Ingresando muchos datos: “Eoln”<br />

En esta sección veremos distintas formas de ingresar muchos datos, que es<br />

una operación muy común en las aplicaciones.<br />

Problema 4.20. Hacer un programa que —usando un lazo “ for ”— calcule e<br />

imprima la suma de cierta cantidad de enteros entrados por terminal. El usuario<br />

debe ingresar primero el número de datos y después cada uno de los datos a<br />

sumar. ✄<br />

Problema 4.21. Cuando entramos datos, muchas veces es molesto contar la<br />

cantidad de datos antes de entrarlos: es mucho más cómodo ingresarlos y dar<br />

una señal al programa de que la entrada de datos ha terminado. Una posibilidad<br />

es usar un “dato imposible” como señal, por ejemplo si se ingresa −1 cuando<br />

los datos a sumar son positivos.<br />

Haciendo esta suposición (i.e. el usuario sólo ingresa los datos a sumar que<br />

son positivos, y la señal de fin de datos que es −1), modificar el programa del<br />

problema 4.20 cambiando el lazo “ for ” por uno “ while ”o“repeat ”.<br />

Sugerencia: agregar una variable booleana findatos.<br />

Sugerencia si la anterior no alcanza: usar un esquema como<br />

suma := 0; findatos := false;<br />

repeat<br />

write(’Entrar un dato (fin = -1): ’); readln(x);<br />

if (x = -1) then findatos := true<br />

else suma := suma + x<br />

until (findatos); ✄<br />

Ciertamente el “dato imposible −1” del problema 4.21 no es muy conveniente,<br />

ya que tendremos que modificar el programa si en algún momento queremos<br />

usarlo para sumar datos que pueden ser negativos. Más cómodo sería ingresar<br />

como “dato imposible” alguna señal que no imponga limitaciones sobre los<br />

númerosaentrar(salvoqueseandetipoenterooreal).<br />

Una posibilidad es poner como “dato imposible” un “dato vacío”, es decir,<br />

nada, o más concretamente un sin entrada de datos, usando la función<br />

de Pascal “ eoln ”.<br />

Pero antes de describir esta función, será conveniente que el lector repase un<br />

poco lo dicho —y hecho— al introducir la función “ readln ” en la sección 3.2.1.


Pág. 38 Tomando control<br />

Cuando ingresamos un dato, éste no es leído por el programa hasta que<br />

ingresemos , dándonos la oportunidad de cambiarlo en caso de error<br />

de tipeo.<br />

Si hemos declarado la variable a (de algún tipo), la sentencia “ readln(a)”<br />

hace que el programa lea el primer dato que no sea justamente ylo<br />

guarde en a si el dato es correcto (si es incorrecto, puede pasar cualquier cosa:<br />

ver el problema 3.3.b)).<br />

Si en el programa fuente tenemos la instrucción “ readln ”, sin argumentos,<br />

entonces se lee el renglón ingresado, y si hemos entrado otros datos antes de<br />

, éstos son ignorados (ver el problema 3.3.c)).<br />

Al ingresar nosotros , nosólo estamos indicando que terminamos<br />

de ingresar nuestros datos, sino que se emite una señal especial que llamaremos<br />

fin de línea.<br />

✎ La señal de “fin de línea” depende del sistema operativo, y puede consisir<br />

de uno o más caracteres.<br />

Cuando ponemos “ readln(a) ” lo que hacemos efectivamente es “leer” la<br />

señaldefindelínea además del dato a, yalponer“readln ” (sin argumentos)<br />

loquesehaceesleersólolaseñaldefindelínea que no se guarda en ninguna<br />

variable.<br />

La función de Pascal “ eoln ” (una abreviatura de “end of line” o fin de<br />

línea), sin argumentos, devuelve un valor lógico, i.e. verdadero o falso, según<br />

haya o no un ofindelínea por leer.<br />

Como “ readln ”, “ eoln ” se queda a la espera de ingreso de datos, que<br />

necesariamente deben incluir un . Si se han ingresado datos antes<br />

de , “eoln ”retornafalso ysino,verdadero. Pero a diferencia de<br />

“ readln ”, “ eoln ” no “lee” el fin de línea, y debemos realizar esta “lectura”<br />

con “ readln ” sin argumentos.<br />

✎ Sin embargo, después de evaluarse “ eoln ”, un nuevo “ readln ” con argumentos<br />

hará que se ignoren los ’s que hayamos introducido<br />

aunque no se hayan “leído”, como ya hemos verificado en el problema 3.3.<br />

Problema 4.22. El programa eolnprueba (pág. 180) es una prueba del funcionamiento<br />

de “ eoln ”, en el que se usa la variable a para entender el flujo de las<br />

instrucciones.<br />

a) Compilar y ejecutar el programa, observando que luego de imprimir el valor<br />

de a antes del lazo, queda a la espera del ingreso de datos:<br />

i) Ingresar “”, sin otros caracteres, y observar el valor final<br />

de a. ¿Cuáles de las instrucciones en “ if ” se ejecutaron?, ¿qué valor<br />

ha dado “ eoln ”yporqué?<br />

ii) Si en vez de ingresar sólo “ ”, se ingresa “ ”<br />

(con un espacio antes), ¿cuál será el valor de a queseimprimeal<br />

final? Verificar la respuesta ejecutando el programa con esa entrada.<br />

b) Después del renglón “ writeln(’Ahora el valor... ” agregar los renglones<br />

write(’Entrar un nuevo valor de a: ’);<br />

readln(a);<br />

writeln(’El valor final de a es ’, a);


4.6. Comentarios Bibliográficos Pág. 39<br />

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

un 3 antes), ¿cuáles son los valores sucesivos de a?, ¿por qué?<br />

ii) Si en la nueva variante ingresamos “ 4” (conun4antes)y<br />

luego “ 5” (con un 5 antes), ¿cuáles son los valores sucesivos<br />

de a?, ¿por qué?<br />

En cada caso ejecutar el programa y verificar la respuesta. ✄<br />

Problema 4.23. El programa sumardatos (pág. 181) calcula la suma de números<br />

reales entrados por terminal, donde para finalizar la entrada de datos se<br />

ingresa sin dato.<br />

✎ Comparar con las sugerencias del problema 4.21.<br />

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

sin dato, y el uso de “ readln ”para“leer”elfindelínea que ha quedado.<br />

¿Qué pasasiseeliminaeste“readln ”?<br />

b) Modificar el programa para que a la salida indique también la cantidad de<br />

datos ingresados.<br />

c) Modificar el programa para que también indique el promedio de los datos<br />

entrados. ✄<br />

Problema 4.24. El lazo “ repeat...until (findatos)” del programa sumardatos:<br />

a) ¿Podría cambiarse por<br />

repeat<br />

write(’Entrar un dato (fin = ): ’);<br />

readln(x); s := s + x<br />

until (eoln) ?<br />

b) ¿Y por<br />

while (not eoln) do begin<br />

write(’Entrar un dato (fin = ): ’);<br />

readln(x); s := s + x<br />

end ?<br />

Explicar en cada caso, eventualmente haciendo un programa para verificar<br />

que las respuestas son correctas. ✄<br />

4.6. Comentarios Bibliográficos<br />

Los comentarios históricos sobre años bisiestos están basados en [13].


Capítulo 5<br />

Aplicaciones<br />

En este capítulo mostramos que se puede hacer bastante matemáticas con<br />

los recursos de programación que hemos visto.<br />

5.1. Cálculo numérico elemental<br />

Ciertamente una de las aplicaciones más importantes de la computadora —y<br />

a la cual debe su nombre— es la obtención de resultados numéricos. A veces es<br />

sencillo obtener los resultados deseados pero a veces —debido a lo complicado<br />

del problema o a los errores numéricos— es sumamente difícil, lo que ha dado<br />

lugar a toda un área de matemáticas llamada “cálculo numérico”.<br />

5.1.1. Mezclando números grandes y pequeños<br />

Sorprendentemente, al trabajar con números de tipo “ real ” pueden pasar<br />

cosas curiosas como:<br />

• al sumar un número grande a uno pequeño (en relación) puede dar nuevamente<br />

el número grande,<br />

• que la diferencia sea 0 aún cuando los números son distintos,<br />

• o que la suma no sea conmutativa.<br />

Por supuesto, ya hemos visto este tipo de comportamiento al calcular εmaq<br />

(problema 4.17), debido fundamentalmente a la densidad variable de números<br />

de tipo “ real ” mencionada en la sección 3.2.3 (pág. 19).<br />

Problema 5.1.<br />

a) Encontrar x de tipo real tal que, para la máquina, ¡x = x +1!.<br />

Sugerencia: buscar una potencia de 2, usando algo como<br />

x := 1; repeat x := 2 * x; y := x + 1 until (x = y)<br />

b) ¿Quérelación hay entre x y1/εmaq? Por ejemplo, ¿es x × εmaq = 1 o<br />

parecido? ✄


5.1. Cálculo numérico elemental Pág. 41<br />

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

armónico se definen como<br />

Hn =1+ 1 1 1<br />

+ + ···+<br />

2 3 n =<br />

n 1<br />

i .<br />

i=1<br />

Así, H1 =1,H2 =3/2, H3 =11/6, H4 =25/12, H5 = 137/60.<br />

a) Desarrollar un programa (siguiendo las ideas del problema 4.13 sobre suma<br />

de Gauss), para calcular Hn aproximadamente.<br />

b) En este caso particular, puede demostrarse que es más preciso hacer la<br />

suma “de atrás hacia adelante” que “de adelante hacia atrás”. Comprobar<br />

que para n grande difieren los resultados realizando las sumas en estas dos<br />

formas.<br />

c) En los cursos de análisis o cálculo matemáticosevequeamedidaquen<br />

crece, Hn crece indefinidamente como ln n.<br />

Ver que efectivamente la diferencia Hn − ln n es aproximadamente constante<br />

para valores grandes de n (esta constante es la constante de Euler<br />

γ = 0.5772156649 ...), por ejemplo calculando las diferencias para n =<br />

100, 1000, 10000. ✄<br />

El siguiente problema muestra algunos de los inconvenientes al restar cantidades<br />

grandes y similares.<br />

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

es sabido, la ecuación cuadrática<br />

ax 2 + bx + c =0 (5.1)<br />

donde a, b, c ∈ R son datos con a = 0, tiene soluciones reales si<br />

no es negativo, y están dadas por<br />

x1 = −b + √ d<br />

2a<br />

d = b 2 − 4ac<br />

y x2 = −b − √ d<br />

. (5.2)<br />

2a<br />

a) Hacer un programa que, dados a, b y c, verifiquesia= 0yd ≥ 0, poniendo<br />

un aviso en caso contrario, y en caso afirmativo calcule x1 y x2 usando las<br />

ecuaciones (5.2), y también las cantidades ax2 i + bxi + c, i =1, 2, viendo<br />

cuán cerca están de 0.<br />

b) Cuando d ≈ b2 , i.e. cuando |4ac| ≪b2 , pueden surgir inconvenientes numéricos.<br />

Por ejemplo, calcular las raíces usando el programa del inciso anterior,<br />

cuando a =1,b = 10000 y c = 1, verificando si se satisface la ecuación (5.1)<br />

en cada caso.<br />

✎ Las soluciones con varios dígitos son:<br />

x1 = −0.000100000001000 ... y x2 = −9999.999899999998999 ...<br />

c) Uno de los problemas en el ejemplo del inciso anterior surge de restar números<br />

grandes que son comparables. El siguiente fragmento de programa alivia<br />

un poco la situación:


Pág. 42 Aplicaciones<br />

if (b > 0) then x1 := -(b + sqrt(d))/(2 * a)<br />

else x1 := (sqrt(d) - b)/(2 * a);<br />

x2 := c / (x1 * a);<br />

i) Justificar esta construcción. Sugerencia: recordar que x1 + x2 = −b/a<br />

y x1x2 = c/a.<br />

ii) Hacer un programa con estas modificaciones y comparar con los resultados<br />

en b).<br />

No es importante recordar el “truco”. El ejemplo está aquí para mostrar<br />

cómo el área de cálculo numérico se preocupa en dar soluciones a las<br />

dificultades computacionales. ✄<br />

El siguiente problema muestra dificultades no sólo computacionales sino también<br />

matemáticas al considerar una sucesiónqueseaproximaa1peroquees<br />

elevada a potencias cada vez más grandes.<br />

Problema 5.4. Consideremos la sucesión<br />

<br />

an = 1+ 1<br />

n , para n ∈ N.<br />

n<br />

a) Hacer un programa para generar an dado n (¿de qué tiposserán n y an),<br />

y usarlo para varios valores de n. En base a la experiencia, ¿podrías decir<br />

que an es creciente (i.e. si an


5.1. Cálculo numérico elemental Pág. 43<br />

hacer un programa que ingresando x ∈ R+ y n ∈ N, calcule<br />

<br />

<br />

<br />

√x<br />

··· (= x 1/2n<br />

).<br />

<br />

n raíces<br />

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

grande dependiendo de x). ¿Qué seobserva? ✄<br />

Como el lector habrá comprobado en el problema anterior, a medida que<br />

aumentamos el número de iteraciones, i.e. el valor de n, nos aproximamos cada<br />

vez más a 1. Por supuesto que si empezamos con x = 1, obtendremos siempre<br />

el mismo 1 como resultado, ya que √ 1=1.<br />

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

punto fijo de f si<br />

f(x) =x,<br />

de modo que 1 es un punto fijo de la función f(x) = √ x.<br />

Problema 5.6. ¿Hay algún otro punto fijo de la raíz cuadrada, además de<br />

x =1? ✄<br />

En lo que resta de la sección trabajaremos con funciones continuas, es decir,<br />

funciones que “pueden dibujarse sin levantar el lápiz del papel”. Ejemplos de<br />

funciones continuas son: cualquier polinomio P (x), |x|, cosx, y √ x (para x><br />

0) que acabamos de ver. Para nosotros será suficiente esta idea intuitiva: la<br />

definición rigurosa de función continua se da en los cursos de análisis o cálculo<br />

matemático.<br />

✎ Por supuesto, hay funciones continuas que “no se pueden dibujar” como<br />

• 1/x para x>0, pues cerca de x = 0 se nos acaba el papel,<br />

• sen 1/x para x>0, pues cerca de x = 0 oscila demasiado,<br />

•<br />

f(x) =<br />

x sen 1/x<br />

0<br />

si x = 0,<br />

si x =0<br />

pues cerca de x = 0 oscila demasiado,<br />

y hay funciones que no son continuas como<br />

• signo(x) parax∈Ê, pues pega un “salto” en x =0,<br />

• la función de Dirichlet,<br />

f(x) =<br />

una función imposible de visualizar.<br />

1 si x ∈ É,<br />

0 si x ∈ Ê \ É,<br />

Muchas funciones de interés interactúan de manera interesante con sus puntosfijoscomoenelcasodelaraíz<br />

cuadrada: supongamos que x0 es un punto<br />

dado o inicial y definimos<br />

x1 = f(x0),x2 = f(x1),...,xn = f(xn−1),...<br />

y supongamos que tenemos la suerte que xn se aproxima o converge a ℓ amedida<br />

que n crece, i.e.<br />

xn ≈ ℓ cuando n es muy grande.


Pág. 44 Aplicaciones<br />

1<br />

0.5<br />

y<br />

y = x<br />

y =cosx<br />

0.5 1 1.5<br />

Figura 5.1: Gráficos de y =cosx y y = x.<br />

Puede demostrarse entonces que, si f es continua, ℓ es un punto fijo de f.<br />

Por ejemplo, supongamos que queremos encontrar x tal que cos x = x. Mirando<br />

el gráfico de la figura 5.1, vemos que efectivamente hay un punto fijo de<br />

f(x) =cosx, i.e. un punto x ∗ tal que f(x ∗ )=x ∗ . Podemos apreciar en el gráfico<br />

que el punto buscado está entre0.5 y1,yprobamoslatécnica mencionada:<br />

dado x0 ∈ R definimos<br />

xn+1 =cosxn<br />

x<br />

para n =0, 1, 2,...,<br />

ytratamosdeversixn se aproxima a algún punto cuando n crece.<br />

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

punto fijo, donde los trazos horizontales van desde puntos en el gráfico de f a<br />

la diagonal y = x y los verticales vuelven al gráfico de f:<br />

1<br />

0.5<br />

y<br />

0.5 1 1.5<br />

Figura 5.2: Aproximándose al punto fijo de cos x.<br />

Problema 5.7 (Punto fijo de f(x) =cosx). Con las notaciones anteriores<br />

para f y xi:<br />

a) Usando un lazo “ for ”, hacer un programa que dados x0 y n calcule xn, y<br />

observar el comportamiento para distintos valores de x0 y n.<br />

b) Modificar el programa para que también imprima cos xn y comprobar que<br />

para n más o menos grande se tiene xn ≈ cos xn.<br />

c) Modificar el programa para hacer 200 iteraciones, mostrando los resultados<br />

intermedios cada 10. Observar que después de cierta cantidad de iteraciones,<br />

los valores de xk no varían.<br />

x


5.1. Cálculo numérico elemental Pág. 45<br />

✎ El valor de x200 es una buena aproximación al único punto fijo de<br />

f(x) =cosx,aún cuando puede ser que x200 = cosx200(= x201) debido<br />

a errores numéricos. El “verdadero” punto fijo es 0.7390851332 ....<br />

d) Modificar el programa de modo que no se hagan más iteraciones si<br />

|xk+1 − xk| 0esunparámetro entrado por el usuario (e.g. ε =0.00001), aún<br />

cuando k sea menor que n.<br />

Sugerencia: hay que cambiar el lazo “ for ” a uno “ while ”o“repeat ”.<br />

Sugerencia si la anterior no alcanza: suponiendo que x0 es el dato inicial<br />

y n el número máximo de iteraciones, usando un lazo “ while ”podríamos<br />

poner:<br />

k := 1; x := x0; y := cos(x);<br />

while ((k < n) and (abs(y - x) >= eps)) do begin<br />

k := k + 1; x := y; y := cos(x)<br />

end<br />

✎ Observar que la condición |xk+1−xk|


Pág. 46 Aplicaciones<br />

sección es una versión actualizada de una técnica usada por los babilónicos hace<br />

miles de años para aproximar a la raíz cuadrada.<br />

Los babilonios tomaron poder de la Mesopotamia (entre los ríos Tigris y<br />

Éufrates) alrededor de 2000 a. de C., desalojando a los sumerios, quienes<br />

habían estado allí desde 3500 a. de C. Fueron los sumerios los que desarrollaron<br />

el sistema sexagesimal (en base 60) que nos llega a la actualidad en la<br />

división de horas en minutos y segundos, y los babilonios continuaron con el<br />

desarrollo del sistema en base 60, llegando a un sistema que en algunos sentidos<br />

era más avanzado que el decimal nuestro.<br />

Pero los babilonios también estudiaron la resolución de ecuaciones cuadráticas<br />

y cúbicas, llegando al equivalente de la ecuación cuadrática (5.1) ymuy<br />

posiblemente al método que presentamos en esta sección para aproximar raíces<br />

cuadradas. El método estaría descripto en la tableta “Yale”, fechada entre 1800<br />

y 1650 a. de C.<br />

Para llegar a este método, los babilonios habrían usado ideas geométricas<br />

para aproximar la media geométrica de los números x y a/x,<br />

Ö a<br />

x ×<br />

x ,<br />

que es el número √ a buscado, por su media aritmética (o promedio),<br />

1<br />

a<br />

x + .<br />

2 x<br />

Mucho más tarde, Isaac Newton (1642–1727) desarrolló unmétodo basado<br />

en el análisis matemático para encontrar raíces de funciones mucho más generales,<br />

que tiene como caso particular al método babilónico. El método de Newton<br />

también es conocido como de Newton-Raphson, pues J. Raphson (1648–1715)<br />

publicó este resultado unos 50 años antes que se publicara el de Newton.<br />

Variantes del método de Newton-Raphson, que se estudia en los cursos<br />

de cálculo numérico, son los que usan las computadoras y calculadoras para<br />

calcular funciones como el seno o el logaritmo.<br />

Problema 5.9 (Método babilónico I). Este método para aproximar la raíz<br />

cuadrada de a ∈ R+, consiste en aplicar sucesivamente las iteraciones<br />

xn = 1<br />

<br />

xn−1 +<br />

2<br />

a<br />

<br />

para n =1, 2,..., (5.3)<br />

xn−1<br />

a partir de un valor inicial x0 dado (x0 > 0). Es decir xn = fa(xn−1), donde<br />

fa : R+ → R+ está definida como<br />

fa(x) = 1<br />

<br />

2<br />

x + a<br />

x<br />

a) Probar que si a>0yx>0, entonces fa(x) > 0, y si fa(x) =x (x es un<br />

punto fijo de fa) entonces x = √ a.<br />

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

xn > 0paratodon∈N, ysixn = √ a para algún n, entonces xn = xn−1 =<br />

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

nunca da la respuesta exacta salvo que x0 = √ a.<br />

c) Bosquejar un gráfico similar al segundo del problema 5.7, cuando a =2y<br />

x0 =1.<br />

d) ¿Quépasasia =0?¿Ysix0 =0?<br />

<br />

.


5.1. Cálculo numérico elemental Pág. 47<br />

e) ¿Quépasasix0 < 0ya>0?<br />

f )¿Quépasasia0).<br />

✎ En el programa original se consideran errores absolutos en vez de relativos.<br />

Estos errores se estudian en cursos de cálculo numérico y estadística,<br />

y también en otras ciencias como física o química.<br />

El procedimiento de normalización o escalado que hicimos en este<br />

inciso es esencial en cálculo numérico: trabajar con papas y manzanas<br />

ynoconátomos y planetas, o, más científicamente, con magnitudes<br />

del mismo orden.<br />

Cuando se comparan papas con manzanas en la computadora, se<br />

tiene en cuenta el valor de εmaq, pero al trabajar cerca del 0 hay que<br />

tener en cuenta a εmin.<br />

f ) Verificar el comportamiento del programa en los casos de los incisos d), e)<br />

y f )delproblema5.9.<br />

✎ En el caso a 1, o en forma oscilatoria, si<br />

f(x) =−x 3 y x0 > 1.<br />

Es decir, un método iterativo puede no converger a una solución.<br />

Por otro lado, como hemos visto también en el problema 5.8, aún


Pág. 48 Aplicaciones<br />

cuando un método iterativo converja a una solución, no siempre obtenemos<br />

el punto fijo que buscamos, salvo que empecemos con un valor<br />

“razonablemente” cercano. ✄<br />

5.2. Números enteros y divisibilidad<br />

No sólo podemos obtener resultados numéricos con la computadora, sino que<br />

podemos inferir resultados teóricos experimentando:<br />

Problema 5.11.<br />

a) Hacer un programa para calcular la suma de los primeros n números impares<br />

positivos (n ∈ N),<br />

tn =1+3+···+(2n − 1) =<br />

n<br />

(2k − 1).<br />

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

a la de Gauss en el problema 4.13.<br />

c) Tratar de demostrar la fórmula. Sugerencia: usarlafórmula de Gauss o, si<br />

se conoce el método, usar inducción. ✄<br />

5.2.1. Ecuaciones diofánticas<br />

k=1<br />

Problema 5.12. Queremos resolver el siguiente problema:<br />

Geri y Guille compraron botellas de vino para la reunión. Ambos<br />

querían quedar bien y Guille gastó $5 por botella, mientras que Geri,<br />

que es más sibarita, gastó $8 por botella. Si entre los dos gastaron<br />

$81, ¿cuántas botellas compró cada uno?<br />

a) Hacer un programa para resolver el problema. Sugerencia: se trata de resolver<br />

la ecuación 5x +8y = 81, con x, y ∈ Z, x, y ≥ 0. Por lo tanto,<br />

0 ≤ x ≤⌊81 5 ⌋. Usando un lazo “ for ”, recorrer los valores de x posibles,<br />

x =0, 1,...,16, buscando y ∈ Z, y ≥ 0.<br />

✎ Hay dos soluciones posibles.<br />

✎ En este caso es más eficiente recorrer los valores de y (desde 0 hasta<br />

⌋ = 10) pues hay menos valores a considerar.<br />

⌊ 81<br />

8<br />

b) Generalizar el programa para resolver ecuaciones de la forma ax + by = c,<br />

donde a, b, c ∈ N son dados por el usuario y x, y son incógnitas enteras no<br />

negativas. El programa debe exhibir todas los pares (x, y) de soluciones o<br />

decir que no hay soluciones posibles.<br />

✎ La estrategia de resolución es recorrer todas las posibilidades, una por<br />

una, y por eso se llama de barrido, pero hay algoritmos más eficientes para<br />

este caso basados en que el máximo común divisor entre a y b, mcd(a, b)<br />

puede escribirse como mcd(a, b) =Aa + Bb para A, B ∈ , y que valores<br />

apropiados de A y B pueden obtenerse mediante el algoritmo de Euclides,<br />

que veremos luego. Los detalles se ven en cursos de matemática discreta o<br />

teoría elemental de números. ✄


5.2. Números enteros y divisibilidad Pág. 49<br />

Diofanto de Alejandría (aproximadamente 200–284 d. de C.) fue el primero<br />

en estudiar sistemáticamente problemas de ecuaciones con soluciones enteras,<br />

siendo autor del influyente libro “Aritmética”. En su honor, las ecuaciones<br />

donde las incógnitas son enteros se llaman diofánticas o diofantinas.<br />

Otras ecuaciones diofánticas bien conocidas son las de la forma x n + y n =<br />

z n , donde las incógnitas son x, y, z ∈ Æ y n ∈ Æ está dado. Cuando n =2,las<br />

soluciones (x, y, z) forman una terna pitagórica, y cuando n>2, tenemos la<br />

célebre ecuación de <strong>Fe</strong>rmat-Wiles, que no tiene soluciones.<br />

Justamente <strong>Fe</strong>rmat (1601–1665) fue quien escribió en el margen de su copia<br />

de la “Aritmética” de Diofanto (traducida por Bachet) sobre la imposibilidad<br />

de resolución de la ecuación x n + y n = z n en enteros cuando n>2:<br />

Encontré una demostración verdaderamente destacable, pero el margen<br />

del libro es demasiado pequeño para contenerla.<br />

Wiles demostró el teorema en 1994, ¡casi 350 años después!<br />

La técnica del problema anterior puede extenderse para “barrer” más de dos<br />

números:<br />

Problema 5.13. En los partidos de rugby se consiguen tantos mediante tries<br />

(5 tantos cada try), tries convertidos (7 tantos cada uno) y penales convertidos<br />

(3 tantos cada uno).<br />

Hacer un programa que ingresando la cantidad total de puntos que obtuvo<br />

un equipo al final de un partido, determine las formas posibles de obtener ese<br />

resultado.<br />

Por ejemplo, si un equipo obtuvo 21 tantos, las formas posibles son:<br />

Posibilidad Tries Tries convertidos Penales<br />

1 0 0 7<br />

2 0 3 0<br />

3 1 1 3<br />

4 3 0 2 ✄<br />

También la técnica de “barrido” puede usarse para ecuaciones no lineales:<br />

Problema 5.14.<br />

a) Hacer un programa para que dado n ∈ N determine si existen enteros nonegativos<br />

x, y tales que x2 + y2 = n, exhibiendo en caso positivo un par de<br />

valores de x, y posibles, y en caso contrario imprimiendo un cartel adecuado.<br />

b) En base a los resultados anteriores, ¿cuáles son los n ∈ N que se pueden<br />

escribir como suma de dos cuadrados?<br />

✎ Se puede demostrar (lo que se ve en teoría elemental de números) que<br />

todo entero positivo es suma de cuatro cuadrados (algunos eventualmente<br />

nulos), y que un entero positivo es suma de dos cuadrados si y<br />

sólosiesdelaformaab, donde a es una potencia de 4 (eventualmente<br />

4 0 =1)yb tiene resto 1 al dividirlo por 4. ✄<br />

5.2.2. Máximo común divisor y el algoritmo de Euclides<br />

Dados a, b ∈ N, elmáximo común divisor entre a y b, mcd(a, b), se define (1)<br />

como el máximo elemento del conjunto de divisores comunes de a y b:<br />

(1) ¡Como su nombre lo indica!<br />

mcd(a, b) =máx D,


Pág. 50 Aplicaciones<br />

donde<br />

y<br />

D = {d ∈ Z : d | a y d | b}.<br />

✎ D= ∅, pues 1 ∈D.Además, D está acotado superiormente (por mín{a, b}),<br />

por lo que mcd(a, b) está bien definido.<br />

Cuando a, b ∈ Z, definimos<br />

mcd(a, b) =mcd(|a|, |b|)<br />

mcd(0,z)=mcd(z,0) = |z|z para todo z ∈ Z, z = 0.<br />

No tiene mucho sentido mcd(0, 0), pero por comodidad ponemos<br />

mcd(0, 0) = 0.<br />

Cuando mcd(a, b) = 1, es usual decir que los enteros a y b son primos entre<br />

sí o coprimos.<br />

En la escuela elemental a veces se enseña a calcular el máximo común divisor<br />

efectuando primeramente la descomposición en primos (2) . Sin embargo, la<br />

factorización en primos es computacionalmente difícil (3) y en general bastante<br />

menos eficiente que el algoritmo de Euclides, que aún después de 2000 años, es<br />

el más indicado (con pocas variantes) para calcular el máximo común divisor.<br />

Euclides (alrededor de 325–265 a. de C., aunque hay discusión sobre si se<br />

trata de una persona o un grupo) escribió una serie de libros de enorme influencia<br />

en las matemáticas, inclusive en las actuales. En la proposición 1 del libro VII,<br />

se enuncia:<br />

Dados dos números distintos, y restando sucesiva y continuamente<br />

el menor del mayor, si el número que queda nunca mide el anterior a<br />

él hasta que queda una unidad, los números originales serán primos<br />

entre sí.<br />

Yenlaproposición 2,<br />

Dados dos números y no primos entre sí, encontrar su máxima medida<br />

común.<br />

procediendo a construirlo. En lenguaje moderno, nos dice que para encontrar<br />

el máximo común divisor, “la máxima medida común”, entre a y b, debemos<br />

restar el menor del mayor hasta que los dos sean iguales.<br />

✎ Por supuesto que Euclides con “números” se refiere a lo que nosotros llamamos<br />

números naturales. En los tiempos de Euclides se pensaba en números<br />

como longitudes de segmentos, y la multiplicación de números es un área.<br />

Un poco antes de Euclides, con Pitágoras y el descubrimiento de la<br />

irracionalidad de √ 2, surgió elproblemadelaconmensurabilidad de segmentos,<br />

es decir si dados dos segmentos de longitudes a y b existe otro de<br />

longitud c tal que a y b son múltiplos enteros de c, i.e. c =mcd(a, b). Si<br />

a es irracional, como √ 2, y b =1,entoncesnoexistec, y el algoritmo de<br />

Euclides no termina.<br />

(2) La definición de número primo está en la sección 5.2.3.<br />

(3) Ver también las notas después del problema 5.20.


5.2. Números enteros y divisibilidad Pág. 51<br />

Problema 5.15 (Algoritmo de Euclides). El programa euclides (pág. 182)<br />

es una versión de este algoritmo para encontrar mcd(a, b) cuando a, b ∈ Z, cambiando<br />

las restas sucesivas por el cálculo del resto mediante la función “ mod ”.<br />

✎ En el problema 4.11 hemos hecho el proceso inverso: para calcular el resto<br />

hicimos restas sucesivas.<br />

a) Teniendo en cuenta la descripción original del algoritmo, ¿sería correcto<br />

cambiar el lazo principal por<br />

(* lazo principal *)<br />

repeat<br />

if (a > b) then a := a - b else b := b - a<br />

until (a = b); ?<br />

¿Y por<br />

(* lazo principal *)<br />

repeat<br />

if (a > b) then a := a - b<br />

else if (a < b) then b := b - a<br />

until (a = b); ?<br />

Explicar en cada caso.<br />

b) Ver que el programa termina en un número finito de pasos, por ejemplo en<br />

no más de |a| pasos.<br />

✎ Observar que en el método babilónico (sección 5.1.3), hemos debido<br />

poner “criterios de parada”, pero no aquí. Recordar también la nota<br />

inmediatamente antes de este problema.<br />

c) Modificar el programa de modo que a la salida escriba la cantidad de veces<br />

que realizó ellazo“while ”.<br />

d) ¿Quépasasise cambia la instrucción “ while (b 0) do ”por“while<br />

(b > 0) do”?<br />

e) ¿Quépasa si se eliminan los renglones donde se consideran los casos a


Pág. 52 Aplicaciones<br />

✎ Recordando el tema de la conmensurabilidad mencionado al introducir el<br />

algoritmo de Euclides, no siempre el problema tiene solución. Por ejemplo,<br />

si Pablito hace 1 metro cada 2 pasos y el papá √ 2 metros cada 2 pasos. ✄<br />

Problema 5.17. El mínimo común múltiplo de a, b ∈ N, mcm(a, b), se define<br />

en forma análoga al máximo común divisor: es el menor entero del conjunto<br />

{k ∈ N : a | k y b | k}.<br />

a) En la escuela nos enseñan (4) que si a, b ∈ N entonces<br />

mcm(a, b) × mcd(a, b) =a × b.<br />

Escribir un programa para calcular mcm(a, b) paraa, b ∈ N, usando esta<br />

relación.<br />

b) ¿Cómo podría extenderse la definición de mcm(a, b) paraa, b ∈ Z? ¿Cuál<br />

sería el valor de mcm(0,z)? ✄<br />

Problema 5.18.<br />

a) ¿Quérelación hay entre el máximo común divisor o el mínimo común múltiplo<br />

con el problema 5.16 (de Pablito y su papá)? Sugerencia: recordando<br />

que el problema no siempre tiene solución, volver a mirar la descripción<br />

dada por Euclides de su algoritmo pensando en números cualesquiera, restando<br />

el mayor del menor hasta llegar a una “medida común”, y quizás el<br />

inciso h) delproblema4.11 (pág. 32).<br />

b) Hacer un programa para resolver ese problema, donde las entradas son el<br />

número de pasos y la cantidad de metros recorridos tanto para Pablito como<br />

para su papá.<br />

✎ Como para la computadora todos los números son racionales, el problema<br />

siempre tiene solución. ✄<br />

5.2.3. Números primos<br />

Recordemos que un número n ∈ N es primo si n>1ysusúnicos divisores<br />

(positivos) son 1 y él mismo. Los primeros primos son 2, 3, 5, 7 y 11.<br />

✎ Algunos autores consideran que −2, −3, etc. también son primos, pero<br />

nosotros los consideraremos siempre mayores que 1.<br />

También en sus libros Euclides demuestra muchos teoremas importantes sobre<br />

números primos: en el Libro VII, Proposiciones 31 y 32, que todo número<br />

se puede escribir como producto de primos, y en el Libro IX, Proposición 20<br />

—con una de las demostraciones más hermosas de las matemáticas— que hay<br />

infinitos primos.<br />

Problema 5.19.<br />

a) Probar que si el entero a>1 no es primo, entonces existen enteros b y c,<br />

ambos mayores que 1, tales que b ≤ √ a y a = b × c. Sugerencia: sia = b × c<br />

y1


5.3. Problemas Adicionales Pág. 53<br />

Problema 5.20. El programa factoresprimos (pág. 183) es una elaboración del<br />

programa en el problema 5.19, e imprime todos los factores primos del número<br />

a>1 entrado por terminal.<br />

a) Ver que efectivamente el programa determina todos los factores primos cuando<br />

a>1.<br />

b) Sin embargo, cuando a =1o0elprogramaretornaa, que no es primo.<br />

c) ¿Quépasa cuando a0 entonces:<br />

a) fa(x) − √ a = (x − √ a) 2<br />

.<br />

2x<br />

b) fa(x) 2 − a = (x2 − a) 2<br />

4x2 .<br />

c) Six> √ a,0< (x − √ a)/x < 1, y |fa(x) − √ a| < 1<br />

2 |x − √ a|, i.e. acortamos<br />

más de la mitad la distancia a √ a.<br />

d) x1 ≥ x2 ≥···≥xn ≥···≥ √ a.


Pág. 54 Aplicaciones<br />

✎ Enloscursosdeanálisis o cálculo matemático se ve que por lo tanto,<br />

para n ≥ 1losvaloresdexn decrecen monótonamente hacia un número<br />

b, ycomofa es “continua”, resultará fa(b) =b = √ a.<br />

e) Si x0 = √ a,paran≥1valen las desigualdades<br />

|xn+1 − √ <br />

1<br />

a| < mín<br />

2 |xn − √ a|, |xn − √ a| 2<br />

2 √ <br />

a<br />

y<br />

4a<br />

.<br />

✎ Decimos que la convergencia del método es global y cuadrática puesto<br />

que se verifica una desigualdad como la primera. ✄<br />

|x 2 n+1 − a| < (x2 n − a) 2<br />

Problema 5.23 (Conjetura de Goldbach). La conjetura de Goldbach, originada<br />

en la correspondencia entre Christian Goldbach y Euler en 1742, dice que<br />

todo número par mayor que 4 puede escribirse como la suma de dos números<br />

primos impares (no necesariamente distintos).<br />

✎ Aún no se ha podido demostrar que la conjetura sea verdadera o falsa.<br />

a) Hacer un programa que dado el número n par, n ≥ 6, lo descomponga en<br />

suma de dos primos impares, exhibiéndolos (verificando la conjetura) o en<br />

su defecto diga que no se puede descomponer así.<br />

b) Relacionado con la conjetura, en 1937 Vinogradov demostró que todo número<br />

impar suficientemente grande puede escribirse como suma de tres primos<br />

impares. Hacer un programa (que podría usar el anterior) para verificar que<br />

todo número n impar, n ≥ 9, puede escribirse como suma de tres primos<br />

impares. ✄<br />

5.4. Comentarios Bibliográficos<br />

Las citas de los libros de Euclides están tomadas de [9].


Capítulo 6<br />

Arreglos<br />

A veces queremos tener muchas variables del mismo tipo, por ejemplo una<br />

lista de los 10 primeros números primos. Escribir un nombre para cada una de<br />

las variables ya sería engorroso, pero ¿qué pasa si ahora el problema requiere<br />

una lista de 100 o 1000 en vez de 10 datos? Para estos casos es conveniente<br />

usar una estructura similar a la de vector, que podríamos pensar como v =<br />

(v1,v2,...,vn), para guardar los datos. En programación, esta estructura se<br />

llama arreglo (unidimensional).<br />

Por ejemplo, los 10 primeros números primos son<br />

2, 3, 5, 7, 9, 11, 13, 17, 19, 23,<br />

que podríamos guardar en el vector o arreglo v =(v1,v2,...,v10) donde<br />

v1 =2, v2 =3, v3 =5, v4 =7, v5 =11,<br />

v6 =13, v7 =17, v8 =19, v9 =23, v10 =29.<br />

6.1. Dimensionamiento de arreglos<br />

En presencia de un arreglo, la máquina guarda lugares consecutivos de memoria,<br />

todos del mismo tipo, a los cuales se accede mediante índices (como<br />

un vector) poniendo “ v[i] ”envezdevi en el programa fuente. Puesto que<br />

ocupan un lugar de memoria, y este lugar depende del tipo de sus elementos<br />

ydelacantidadodimensión, debemos hacer una declaración apropiada en el<br />

encabezamiento del programa.<br />

Así, en el caso querer guardar en el arreglo v =(v1,v2,...,v10) los primeros<br />

10 primos (que son enteros), haríamos la declaración<br />

v: array[1..10] of integer;<br />

La parte “ 1..10 ” indica el rango donde se moverán los índices. A diferencia<br />

de los vectores de matemáticaofísica, cuyos índices siempre empiezan con 1,<br />

podemos hacer que los índices tengan cualquier rango, e inclusive que sean negativos,<br />

poniendo por ejemplo “ -30..20 ”, aunque nosotros casi siempre usaremos<br />

arreglos con índices que empiezan en 1 o 0.<br />

Sin embargo,


Pág. 56 Arreglos<br />

Los índices de los arreglos deben ser de tipo entero<br />

(“ integer ”), por lo que los rangos posibles dependen<br />

de maxint.<br />

Para guardar los 10 primeros primos en v, habiéndolo declarado como antes,<br />

haríamos las asignaciones:<br />

v[1] := 2; v[2] := 3; v[3] := 5; v[4] := 7;<br />

v[5] := 11; v[6] := 13; v[7] := 17; v[8] := 19;<br />

v[9] := 23; v[10] := 29;<br />

En la figura 6.1 esquematizamos cómo el arreglo v se guarda en la memoria<br />

de la máquina: las componentes tienen identificadores v[1], v[2], etc., y ocupan<br />

lugares consecutivos del mismo tipo.<br />

v<br />

2<br />

v[1]<br />

3<br />

v[2]<br />

5 7 11 13 17 19 23 29<br />

v[3] v[4] v[5] v[6] v[7] v[8] v[9] v[10]<br />

Figura 6.1: Esquema del arreglo v guardado en memoria.<br />

Problema 6.1. Supongamos que en una serie de números queremos encontrar<br />

cuántos terminan en 0, cuántos en 1, etc.<br />

a) Hacer un programa para realizar esta tarea, sin usar arreglos, donde la<br />

lectura y fin de datos se haga en forma similar al programa sumardatos<br />

(pág. 181). Al terminar de ingresar los datos, el programa debe imprimir la<br />

cantidad de datos que terminaron en 0, 1,...,9.<br />

b) En el programa unidades (pág. 184) se hace el mismo programa, pero usando<br />

arreglos.<br />

• En vez de declarar un contador para cada dígito 0, 1,...,9, declaramos<br />

el arreglo terminaen que nos provee de los 10 contadores necesarios.<br />

Así, la dimensión del arreglo terminaen es 10, y sus índices varían<br />

entre 0 y 9.<br />

• Elusodelarreglonosólo facilita la declaración, sino que después<br />

es mucho más sencillo incrementar el contador pues no tenemos que<br />

hacer una serie de “ if ”’s para determinar el contador adecuado.<br />

• Al comenzar el programa los valores del arreglo no están definidos, y<br />

hay que inicializarlos.<br />

• El ingreso de datos es similar al del programa sumardatos, conunlazo<br />

“ while ”envezde“repeat ” (recordar el problema 4.24).<br />

¿Por qué no se pone directamente “ digito := dato mod 10 ”envez<br />

de “ digito := abs(dato) mod 10 ”paradeterminarelúltimo dígito?<br />

c) El arreglo terminaen está declarado de modo que sus índices puedan variar<br />

entre 0 y 9. Agregar el renglón<br />

writeln(’indice 10: ’, terminaen[10]);


6.2. Búsqueda Lineal Pág. 57<br />

antes del renglón “ writeln; writeln(’** Fin **’) ” y ver que los valores<br />

que se obtienen no tienen sentido (si es que el compilador no se queja). ✄<br />

Muchas veces necesitaremos un arreglo con, digamos, n elementos, pero no<br />

conocemos n de antemano, y es muy fácil cometer errores en la programación<br />

porque podríamos tratar de usar una componente que no existe. Por ejemplo si<br />

declaramos “ a: array[1..10] of integer ”, sería un error poner “ a[0] :=<br />

1 ”o“a[20] := 0 ”, como observamos al final del problema 6.1.<br />

✎ Algunos compiladores pueden agregar instrucciones de modo de comprobar<br />

que cuando se corra el programa se verifique que los índices están dentro<br />

del rango permitido. Por supuesto, esto hace que la ejecución del programa<br />

sea más lenta.<br />

En estos casos tratamos de hacer la declaración para guardar d elementos,<br />

con d prudentemente más grande que el rango con el que pensamos trabajar.<br />

Claro que si hemos declarado el arreglo con d = 100 elementos y usamos sólo<br />

n = 20 lugares, estamos desperdiciando lugar.<br />

Como el cambio de dimensión es frecuente, y sobre todo cuando hay muchos<br />

arreglos con la misma dimensión, es usual declarar una constante, mediante<br />

“ const ”, para indicar las dimensiones y mantenerlas en un lugar destacado del<br />

programa. Así, es conveniente hacer las declaraciones<br />

.<br />

const MAXN = 20;<br />

.<br />

var ..<br />

a: array[1..MAXN] of integer;<br />

.<br />

.<br />

Si queremos usar un arreglo mayor, bastará cambiar el valor de MAXN .<br />

Veamos estas ideas en un ejemplo concreto.<br />

6.2. Búsqueda Lineal<br />

Problema 6.2 (Búsqueda lineal). Una de las primeras aplicaciones de arreglos<br />

es ver si cierto elemento aparece o no en el arreglo, a veces llamado problema<br />

de búsqueda. Más concretamente, dados un arreglo a =(a1,a2,...,an) y<br />

un objeto x (del mismo tipo que los elementos de a), queremos ver si existe i,<br />

1 ≤ i ≤ n, talquex = ai.<br />

La forma más sencilla de hacer la búsqueda es comparar sucesivamente o<br />

linealmente los elementos a1,a2,...,an del arreglo con x, técnica que se llama<br />

de búsqueda lineal.<br />

✎ Como veremos más adelante, podríamos pensar en otros tipos de estrategias<br />

para la búsqueda, como al azar olabinaria.<br />

El programa busquedalineal (pág. 184) muestraestatécnica, donde se ingresa<br />

un arreglo de ndatos enteros y el número x a buscar. Observar que:<br />

• Se leen datos como en el programa sumardatos (pág. 181), donde el fin de<br />

la entrada de datos se señala con sin datos. Como el número


Pág. 58 Arreglos<br />

de dato a ingresar es uno más que el número de datos ya ingresado, se<br />

mantiene ndatos “adelantado” en 1, y al terminar se lo retrocede.<br />

• El programa decide con un lazo “ repeat ”sixesonounelementodelarre glo recorriendo el arreglo linealmente, es decir comparando sucesivamente<br />

a1,a2,...,andatos con x, terminando cuando se encuentra coincidencia o<br />

cuando se han analizado todos los elementos.<br />

a) Sin embargo, al ingresar datos no se verifica si el número de datos leído es<br />

ya MAXN . Modificar el lazo de modo de que esa constante sea tenida en<br />

cuenta (impidiendo que se entren más de MAXN datos).<br />

b) Manteniendo la modificación anterior, cambiar el programa para que en vez<br />

de ingresar un arreglo de longitud máxima 20, se pueda ingresar un arreglo<br />

de longitud máxima 50. Sugerencia: sólo habrá quecambiarunnúmero.<br />

c) Observar que en la impresión del arreglo inicial se usa la función “ mod ”<br />

para imprimir un máximo de 5 datos por renglón.<br />

i) ¿Por quéseponeelrenglón<br />

if ((ndatos mod 5) 0) then writeln; ?<br />

ii) El 5 que indica la cantidad de datos por renglón se usa en dos lugares,<br />

de modo que si se cambia el 5 por otro número, hay que acordarse de<br />

cambiar ambos.<br />

Definir la constante maxrenglon al principio del programa para<br />

eliminar este problema.<br />

iii) Ahora modificar el programa de modo de imprimir 8 datos por renglón<br />

como máximo, cambiando el programa en un único lugar.<br />

d) Para buscar x en el arreglo, se usa un lazo “ repeat ”. Cambiarlo por uno<br />

“ while ” (¡y que el programa siga funcionando correctamente!). Sugerencia:<br />

mirar el programa unidades (pág. 184).<br />

e) Cambiar el lazo principal de modo que, de estar x en el arreglo, se obtenga<br />

el último índice i tal que ai = x, envezdelprimero como hace el programa.<br />

Sugerencia: empezar recorriendo desde atrás.<br />

f ) ¿Podría cambiarse la última parte del programa original por<br />

(* lazo principal *)<br />

i := ndatos;<br />

while ((a[i] x) and (i > 1)) do i := i - 1;<br />

(* resultados *)<br />

if (x a[i]) then writeln(’no se encontro’)<br />

else writeln(’se encontro en el lugar ’, i:1) ?<br />

En caso negativo, decir por qué no, y en caso afirmativo, qué ventajasy<br />

desventajas tendría. Sugerencia: ¿cuál es el último valor de i si x no está en<br />

el arreglo?<br />

g) Modificar el lazo principal de modo que al terminar el programa diga cuántas<br />

veces aparece x en el arreglo, y los índices correspondientes (i.e. los i para<br />

los cuales ai = x).<br />

✎ Observar que el lazo principal cambia sustancialmente.<br />

Sugerencia: una posibilidad es ir imprimiendo i a medida que aparece. Otra<br />

posibilidad es agregar un arreglo para guardar los índices i, por ejemplo:


6.3. Cribas Pág. 59<br />

(* lazo principal *)<br />

veces := 0;<br />

for i := 1 to ndatos do<br />

if (a[i] = x) then begin<br />

veces := veces + 1;<br />

indice[veces] := i<br />

end;<br />

y luego imprimir el arreglo indice (entre 1 y veces si veces > 0). Observar<br />

que en esta variante no es necesaria la variable seencontro. ✄<br />

Problema 6.3. Hacer un programa que dados los arreglos a =(a1,a2,...,am)<br />

y b =(b1,b2,...,bn) (ambos dimensionados por MAXN ), forme un tercer arreglo<br />

c =(c1,c2,...,cm+n) (dimensionado por 2 × MAXN ), consistente en los<br />

elementos de a seguidos por los de b.<br />

Por ejemplo, si<br />

tendremos m =4,n =5,y<br />

a =(1, 3, 5, 3) y b =(7, 1, 5, 8, 3),<br />

c =(1, 3, 5, 3, 7, 1, 5, 8, 3).<br />

Hacer el ingreso de datos (para a y b) repitiendo el esquema del programa<br />

busquedalineal (pág. 184). ✄<br />

Problema 6.4. Hacer un programa que dado el arreglo a lo “purgue”, i.e. elimine<br />

los elementos repetidos. Por ejemplo, si inicialmente a =(3, 5, 2, 6, 2, 1, 3, 2),<br />

al final del programa debe ser a =(3, 5, 2, 6, 1). Sugerencia: “buscar” cada elemento<br />

entre los ya puestos para decidir si agregarlo o no. ✄<br />

6.3. Cribas<br />

Cuando debemos seleccionar en un arreglo todos los elementos que satisfacen<br />

cierto criterio, y el criterio para cada elemento depende de los elementos que le<br />

precedieron, es conveniente usar una criba (o cedazo o tamiz). Quizás la más<br />

conocida de las cribas sea la atribuida a Eratóstenes para encontrar los números<br />

primos entre 1 y n (n ∈ N), donde el criterio para decidir si un número k es<br />

primo o no es la divisibilidad por los números que le precedieron.<br />

Eratóstenes (contemporáneo de Arquímedes y posterior a Euclides) nació en<br />

Cirene (en Libia de hoy) alrededor del 276 a. de C.. Vivió gran parte de su<br />

vida en Alejandría (Egipto), donde murió alrededor de 194 a. de C., después de<br />

haber sido bibliotecario de la famosa biblioteca. Hizo importantes contribuciones<br />

en matemáticas, astronomía, geografía y filosofía. Por ejemplo, fue el primero<br />

en medir correctamente la circunferencia de la Tierra (y pensar que Colón<br />

usaba un huevo, ¡1700 años después!).<br />

Problema 6.5 (Criba de Eratóstenes). Supongamos que queremos encontrar<br />

todos los primos menores o iguales que un dado n ∈ N. Una posibilidad es<br />

verificar si cada uno de los números 2, 3, 4,... es primo, usando la técnica del<br />

problema 5.19. Sin embargo, una forma mucho más eficiente es hacer la criba<br />

de Eratóstenes.<br />

La idea es empezar con la lista


Pág. 60 Arreglos<br />

2 3 4 ... n.<br />

Recuadramos 2, y tachamos los restantes múltiplos de 2 de la lista, quedando<br />

2 3 4 5 6 ...<br />

Ahora miramos al primer número que no está marcado (no tiene recuadro<br />

ni está tachado): 3. Lo recuadramos, y tachamos de la lista todos los múltiplos<br />

de 3 que quedan sin marcar. La lista ahora es<br />

2 3 4 5 6 7 8 9 10 11 12 ...<br />

y seguimos de esta forma, recuadrando y tachando múltiplos hasta agotar la<br />

lista.<br />

a) Ver que el algoritmo es correcto, i.e. los números recuadrados que quedan<br />

finalmente son todos los primos que no superan n.<br />

b) Ver que el programa eratostenes (pág. 186) es una implementación de esta<br />

idea, donde indicamos que un número está tachado o no con el arreglo<br />

esprimo que tiene valores booleanos. Observar también que el arreglo<br />

está dimensionado como “ 2..MAXP ”, es decir, no existe “ esprimo[1]”.<br />

c) ¿Quépasasien el lazo principal se elimina la variable i reemplazándola<br />

directamente por p, poniendo “ for p := 2 to MAXP do ”, etc.?<br />

d) Observamos que podemos mejorar el programa de dos formas:<br />

i) Podemos empezar directamente diciendo que 2 es primo y considerar<br />

sólo la lista de impares para recuadrar y tachar, reduciendo el tamaño<br />

de la lista inicial esencialmente a la mitad.<br />

ii) En el algoritmo original tachamos demasiadas veces un mismo número.<br />

Por ejemplo 105 = 3 × 5 × 7 se tacha al considerar 3, 5 y 7. Vemos<br />

que sólo es necesario tachar los múltiplos de, digamos, 7 a partir de<br />

49 = 7 × 7. Claro que tenemos que tener cuidado de no multiplicar<br />

un entero por sí mismo si el producto supera maxint (recordando los<br />

problemas 3.11 y 4.15).<br />

Introducir estas modificaciones en el programa eratostenes, cambiando o<br />

agregando las siguientes sentencias en lugares apropiados:<br />

• const MAXN = 4999; (* = (MAXP-1) div 2,<br />

= tama~no del arreglo esprimo *)<br />

• maxp, raizdemaxint: integer;<br />

• esprimo: array[1..MAXN] of boolean;<br />

• maxp := 2 * MAXN + 1;<br />

• (* inicializacion *)<br />

for i := 1 to MAXN do esprimo[i] := true;<br />

cuenta := 1; (* el primer primo es 2 *)<br />

raizdemaxint := trunc(sqrt(maxint));<br />

• (* lazo principal *)<br />

for i := 1 to MAXN do<br />

if (esprimo[i]) then begin<br />

(* p = 2i + 1 es primo *)<br />

cuenta := cuenta + 1; p := 2*i + 1;


6.3. Cribas Pág. 61<br />

(* ahora eliminar multiplos impares de p *)<br />

if (p


Pág. 62 Arreglos<br />

1<br />

3<br />

4<br />

5<br />

Figura 6.2: Esquema del problema de Flavio Josefo con n =5ym =3.<br />

4<br />

2<br />

5<br />

3<br />

b) ¿Cuál es el sobreviviente si n = 1234 y m =3?Sugerencia: no imprimir<br />

todos los valores, y tener cuidado con el dimensionamiento.<br />

c) Modificar el programa de modo que responda cuántas vueltas sobrevivió la<br />

persona que estaba inicialmente en el lugar s (el usuario entrará n, m y s).<br />

d) Modificar el programa de modo de imprimir una tabla, dando la posición<br />

inicial y el orden de ejecución, algo como<br />

Posición en el círculo Orden de ejecución<br />

1 2<br />

2 4<br />

3 1<br />

4 5<br />

5 3<br />

cuando n =5ym =3.<br />

Sugerencia: eliminar el arreglo flavio y usar otro, digamos orden, demodo<br />

que “ orden[i]” indicará enquévuelta fue eliminado i. Siseinicializa<br />

“ orden[i] := n ”paratodoi, y consideramos que en cada vuelta<br />

“ orden[i] = n ” indica que vive, tampoco es necesario el arreglo vive ni el<br />

lazo para encontrar el sobreviviente: será elúnico con “ orden[i] = n ”al<br />

terminar.<br />

e) ¿Cómo podría modificarse el programa, de modo que siempre se trabaje<br />

con un arreglo (o sub-arreglo) de longitud (por ejemplo) vivos, demodode<br />

no tener que considerar los que ya no viven? Sugerencia: ir “corriendo” las<br />

posiciones en el arreglo a medida que “se cierra” el círculo.<br />

✎ En el problema 14.5 veremos que las listas encadenadas se adaptan<br />

mejor que los arreglos para esta variante.<br />

f ) El lazo “repeat...until (cuenta = m) ” en el programa original es muy<br />

primitivo. Por ejemplo, si n = 1000, m = 100, cuando quedan 10 sobrevivientes<br />

debemos pasar unas 10 veces por cada uno de los 1000 elementos.<br />

¿Podría mejorarse el lazo usando “ mod ”? Sugerencia: recordar el programa<br />

resto (pág. 177). ✄<br />

6.4. Polinomios<br />

¿Para qué estudiar polinomios? ¿No basta con estudiar las funciones lineales<br />

(y = ax + b) yalosumolascuadráticas (y = ax 2 + bx + c)?<br />

¡Humm! Por un lado, los polinomios son las funciones más sencillas que podemos<br />

considerar, para su cálculo sólo se necesitan sumas y productos. Además<br />

1<br />

2


6.4. Polinomios Pág. 63<br />

los usamos diariamente, por ejemplo un númeroenbase10esenrealidadun<br />

polinomio evaluado en 10.<br />

Pero también los polinomios sirven para aproximar tantocomosedeseea<br />

casi cualquier función, lo que constituye un tema central de las matemáticas, y<br />

se estudia tanto en los cursos teóricos de análisis matemático como en los aplicados<br />

de análisis numérico. A modo de ejemplo visual, en la figura 15.1 mostramos<br />

cómo se podría aproximar a la función seno mediante los denominados polinomios<br />

interpoladores de Lagrange.<br />

Nuestra primer tarea será evaluar un polinomio dado:<br />

Problema 6.8 (Evaluación de Polinomios). Hacer un programa que dada<br />

una lista de coeficientes (reales) de un polinomio, (a0,a1,a2,...,an) yunnúmero<br />

x ∈ R, entrados por terminal, evalúe el polinomio anx n + an−1 x n−1 +<br />

···+ a1x + a0.<br />

Hacerlo de tres formas:<br />

a) Calculando la suma de términos como se indica, calculando x k como en el<br />

problema 4.18.<br />

b) Como el anterior, pero las potencias en la forma x k+1 = x k × x, guardando<br />

x k en cada paso.<br />

c) Usando la regla de Horner<br />

((···((an x + an−1) x + an−2) +···) x + a1) x + a0.<br />

✎ En las dos primeras versiones se hacen n sumas, n productos y se calculan<br />

n − 1 potencias, que en la primera versión representan otros 1 + 2 + ···+<br />

(n − 1) = n(n − 1)/2 productos, mientras que en la segunda, los productos<br />

provenientes de las potencias se reducen a n − 1. Finalmente, en la regla<br />

de Horner, tenemos sólo n sumas y n productos.<br />

William George Horner (1786–1837) fue indudablemente una persona muy<br />

capaz: a los 18 años era director de la escuela Kingswood (en Bristol, Inglaterra).<br />

Sin embargo, sus contribuciones matemáticas no han sido demasiadas, y<br />

conservamos el nombre de “regla de Horner” pues De Morgan la denominó asíy<br />

le dio amplia difusión en los muchos artículos que escribió. Sin embargo, el<br />

método era conocido por Zhu Shijie unos 500 años antes. ✄<br />

Problema 6.9 (Escritura en base entera). La idea de la regla de Horner<br />

se usa también en el caso de escritura en base b (entero > 1). Si n ∈ Z es<br />

no-negativo y<br />

n =<br />

k<br />

aib i , donde ai ∈ Z y0≤ ai


Pág. 64 Arreglos<br />

y para encontrar n dados los coeficientes en base b (si ak es el último coeficiente<br />

no nulo)<br />

j := k; n := a[j];<br />

while (j > 0) do begin j := j-1; n := n * b + a[j] end<br />

a) Implementar dos programas para que dados la base b y n, encontrar los coeficientes<br />

ai, yrecíprocamente, dados la base b y los coeficientes ai, calcular<br />

n usando la regla de Horner.<br />

b) En la ecuación (6.1), ¿cómo se relacionan k ylogbn (si ak = 0)? ✄<br />

Problema 6.10. De la escuela recordamos que todo número racional p/q ∈ Q<br />

tiene una “expresión decimal periódica”. Por ejemplo, 1/7 =0.14285701428 ...<br />

tiene período 142857, 617/4950 = 0.124646 ... tiene período 46, 1/4 =0.25 =<br />

0.2500 ... tiene período 0.<br />

✎ Inclusive es posible que recordemos que hay una regla para reconstruir la<br />

fracción dados el de período y el anteperíodo.<br />

Hacer un programa que dados p, q ∈ N, calcule el período (en base 10) de la<br />

fracción p/q.<br />

Sugerencia: poniendo r0 = resto de dividir p por q, los sucesivos dígitos di de<br />

la parte decimal se obtienen de la ecuación<br />

10rn−1 = dnq + rn,<br />

para n =1, 2,..., y hay que buscar (por ejemplo linealmente) si un resto se<br />

vuelve a repetir.<br />

Sugerencia si la anterior no alcanza: Luego de ingresar p y q, poner<br />

n := 0; r[0] := p mod q;<br />

repeat<br />

c := 10 * r[n];<br />

n := n + 1;<br />

d[n] := c div q;<br />

r[n] := c mod q;<br />

(* ahora ver si r[n] ya estaba en r *)<br />

i := 0;<br />

while (r[i] r[n]) do i := i + 1<br />

until (i < n);<br />

El período está formado por los dígitos di+1,...,dn.<br />

✎ A fin de evitar problemas de dimensionamiento al usar arreglos, considerar<br />

q ≤ 1000: para fracciones de la forma p/q con q ≤ 1000 la longitud del<br />

período y anteperíodo es a lo sumo de 982 (cuando q = 983). ✄<br />

6.5. Problemas Adicionales<br />

Problema 6.11. Modificar la criba de Eratóstenes para determinar cuántos de<br />

los 1000 primeros primos terminan respectivamente en 1, 3, 7 y 9.<br />

✎ Observar que, al menos en el rango considerado, la distribución entre los<br />

queterminanen1,3,7o9esmuypareja.


6.6. Comentarios Bibliográficos Pág. 65<br />

El matemático Johann Peter Gustav Lejeune Dirichlet (1805–1859) nació<br />

enDüren que en aquel entonces pertenecía al imperio francés pero hoy<br />

está enAlemania,ydeallíqueavecessedicequeesfrancés y otras veces<br />

que es alemán. El primer trabajo que publicó fue sobre el teorema de <strong>Fe</strong>rmat-<br />

Wiles, resolviendo el caso n =5, pero también trabajó en temas de análisis<br />

(anticipando las “series de Fourier”).<br />

Dirichlet demostró en 1837 que toda progresión aritmética a + bk, k =<br />

1, 2,..., contiene infinitos primos si mcd(a, b) =1(el resultado fue conjeturado<br />

por Gauss e implica el resultado de Euclides sobre que hay infinitos primos).<br />

En el presente problema, si pensamos que b =10,ya es cualquier número<br />

que no tenga como factores a 2 ni a 5, este teorema de Dirichlet dice que hay<br />

infinitos primos que terminan en 1, infinitos que terminan en 3, etc. ✄<br />

Problema 6.12 (Potencias binarias). Otra variante de la idea de la regla de<br />

Horner (problema 6.8) eselcálculo de una potencia “pura”. Si x ∈ R y n ∈ N,<br />

podemos calcular xn escribiendo n en base 2:<br />

y hacer<br />

n =<br />

k<br />

ai2 i ,<br />

i=0<br />

x n = x a0+2a1+22a2+···+2 k−1 ak−1+2 k ak =<br />

= x a0 · (x 2 ) a1 · (x 4 ) a2 ···(x 2k−1<br />

) ak−1 2<br />

· (x k<br />

) ak .<br />

a) Implementar un programa para el cálculo de potencias “puras” con estas<br />

ideas.<br />

✎ Si los coeficientes se conocen, un esquema es<br />

if (a[0] = 1) then producto := x else producto := 1;<br />

potencia := x;<br />

for j := 1 to k do begin<br />

potencia := potencia * potencia; (* = x**{2**j} *)<br />

if (a[j] = 1) then producto := producto * potencia<br />

end<br />

mientras que si los coeficientes no se conocen (encontrando aj en cada<br />

paso):<br />

m := n; potencia := x;<br />

if (m mod 2 = 1) then producto := x else producto := 1;<br />

while (m > 0) do begin<br />

m := m div 2; potencia := potencia * potencia;<br />

if (m mod 2 = 1) then producto := producto * potencia<br />

end<br />

b) Comparando con el problema 4.18, donde se usaba un lazo “ for ”: ¿cuántos<br />

operaciones aritméticas (productos y divisiones) se realizan en cada caso?<br />

(Recordar el problema 6.9.b)).<br />

✎ Debido a su alta eficiencia, la misma técnica se usa para encriptar mensajes<br />

usando números primos de varias decenas de cifras. ✄<br />

6.6. Comentarios Bibliográficos<br />

Los problemas 6.6 y 6.7 están tomados de [4].


Capítulo 7<br />

Funciones y Procedimientos<br />

Nuestros programas se han hecho cada vez más largos, y a medida que avancemos<br />

lo serán aún más. La longitud y complejidad de los programas profesionales<br />

es tal que una sola persona no puede siquiera escribirlos completamente.<br />

A fin de abordar esta dificultad, se han desarrollado una serie de técnicas, y<br />

en este capítulo daremos los primeros pasos hacia una de ellas, la modularización,<br />

para lo cual Pascal cuenta con dos mecanismos: funciones y procedimientos.<br />

Aunque la ventaja de usar funciones o procedimientos irá quedando más<br />

clara con los ejemplos que veremos en éste y otros capítulos, podemos decir que<br />

en general son convenientes para:<br />

• Poner en un único lugar cálculos idénticos que se realizan en distintas<br />

partes del programa,<br />

• o poner por separado alguna acción permitiendo su fácil reemplazo (y con<br />

menor posibilidad de error),<br />

• y no menos importante, haciendo el programa más fácil de entender, dejando<br />

una visión más global y no tan detallada en cada parte (viendo el<br />

bosque y no el árbol).<br />

El lector seguramente recordará el uso que con el mismo espíritu hemos dado<br />

a“const ”, por ejemplo en el problema 6.2, para hacer un único cambio que<br />

afecta a varias partes del programa.<br />

7.1. Funciones<br />

Como hemos visto, Pascal cuenta con una serie de funciones pre-definidas,<br />

como “cos” o “ln”, de una variable, o la suma, “+”, que se aplica a dos o más<br />

variables. Pero —necesariamente— no puede tener todas las funciones y sólo<br />

algunas se han incluido. Por ejemplo, el cálculo de la potencia x n cuando x ∈ R<br />

y n ∈ N no está incluida en Pascal, pero lo hemos hecho en el problema 4.18.<br />

Supongamos que queremos hacer un programa para comparar los valores x n<br />

y y m , donde x, y ∈ R y n, m ∈ N son datos ingresados por el usuario. Nuestro<br />

programa tendría los siguientes pasos:<br />

1. Poner carteles iniciales.


7.1. Funciones Pág. 67<br />

2. Leer los datos x y n, yluegolosdatosyym. 3. Calcular xn y ym .<br />

4. Comparar el resultado, imprimiendo la decisión.<br />

5. Imprimir un cartel final.<br />

Cada una de las acciones mencionadas no es difícil. Por ejemplo para calcular<br />

xn y ym podríamos poner<br />

xn := 1; for k := 1 to n do xn := xn * x;<br />

ym := 1; for k := 1 to m do ym := ym * y;<br />

Problema 7.1. En el programa potencias2 (pág. 188) hemos implementado<br />

estas ideas. Leerlo, reconociendo la estructura mencionada, y ejecutarlo a fin de<br />

verificar su funcionamiento. ✄<br />

Claro que si contáramos con una función de Pascal para calcular xn para<br />

x ∈ R y n ∈ N, digamos “ potencia(x,n)”, podríamos reemplazar los dos<br />

renglones anteriores por<br />

xn := potencia(x,n); ym := potencia(y,m);<br />

Aunque Pascal no cuenta con una funciónquerealiceestecálculo, nos da un<br />

mecanismo para definirla nosotros.<br />

En Pascal, las funciones (y procedimientos que veremos en un rato) se declaran<br />

en la parte declarativa (1) del programa, formando un bloque de estructura<br />

similar a la de los programas.<br />

Por ejemplo, para definir la función potencia (2) pondríamos antes del cuerpo<br />

principal del programa,<br />

function potencia(a: real; b: integer): real;<br />

var k: integer; z: real;<br />

begin<br />

z := 1;<br />

for k := 1 to b do z := z * a;<br />

potencia := z<br />

end;<br />

Observamos que<br />

• Se comienza poniendo el nombre de la función, poniendo “ function ”(o<br />

“ procedure” para procedimientos) en vez de “ program ”, luego una parte<br />

de declaraciones propias, y después un cuerpo principal encerrado entre<br />

“ begin ”y“end; ”(con“; ”envezde“. ”porquenoeselfinaldel<br />

programa).<br />

• Los argumentos de la función deben ser declarados. En nuestro ejemplo hay<br />

dos argumentos, a, detipo“real ”, y b,detipo“integer ”. Por supuesto,<br />

el orden de los argumentos es importante: 2 3 =3 2 . Estos argumentos son<br />

equivalentes al “ (input, output) ” de un programa Pascal.<br />

• Las funciones en Pascal calculan un valor de alguno de los tipos elementales<br />

boolean, char, integer o real.<br />

(1) ¿Podría ser de otro modo?<br />

(2) Usaremos estetipodeletra para señalar que se trata de una función o procedimiento hecho<br />

por nosotros.


Pág. 68 Funciones y Procedimientos<br />

✎ También podrían ser de tipo “ text ”, que veremos en el capítulo 8,<br />

o puntero, que veremos en el capítulo 14. Según el estándar no deben<br />

ser de otro tipo, como arreglos, aunque muchos compiladores lo<br />

permiten.<br />

El tipo del valor calculado debe ser declarado, lo que se hace mediante<br />

la colocación de “ : ” luego de la declaración de argumentos. En nuestro<br />

ejemplo, el resultado es de tipo “ real ”.<br />

Decimos que la función devuelve o retorna el valor calculado.<br />

• Dentro del bloque correspondiente a la función,elnombredeésta debe<br />

ser asignado, y será el valor “retornado” a otras partes del programa.<br />

✎ Sin embargo, el nombre de la función no es el identificador de una<br />

variable. Ver el problema 7.2.d).<br />

✎ No es importante el lugar de la asignación dentro de la función, y<br />

no tiene por qué serlaúltima instrucción en la función. Inclusive es<br />

posible hacer varias asignaciones, pero al menos debe haber una que<br />

se ejecute antes de que termine la función.<br />

• Además de los argumentos y el valor a retornar, la función puede tener<br />

otras variables propias. En nuestro ejemplo, k de tipo “ integer ”yz de<br />

tipo “ real ”. Todas estas variables propias, en nuestro ejemplo a, b, k y<br />

z, se llaman locales, y se desconocen fuera de la función.<br />

• Finalmente, el valor calculado por la función tiene que ser asignado (o<br />

impreso) dentro de la parte principal del programa.<br />

✎ O en otras funciones o procedimientos declaradas posteriormente.<br />

Veremos ejemplos más adelante.<br />

En estos casos decimos que el programa llama o invoca alafunción. Por<br />

ejemplo, llamaríamos a la función potencia con la instrucción<br />

xn := potencia(x,n)<br />

Problema 7.2. En el programa potencias3 (pág. 188) hemos incorporado la<br />

función “ potencia” y los cambios mencionados. Leerlo, estudiando las distintas<br />

instrucciones, y ejecutarlo.<br />

a) Observar que la variable k ya no está definida al comienzo del programa,<br />

sino sólo en la función “ potencia”: es local alafunción pero desconocida<br />

para el resto del programa.<br />

i) Incluir el renglón<br />

writeln(’ El valor de k es: ’, k:1);<br />

inmediatamente antes del cartel final del programa. El compilador seguramente<br />

rechazará esta acción pues k no está declarada.<br />

ii) Manteniendo el renglón problemático, incluir la declaración de k como<br />

estaba en el programa potencias2, y hacer la asignación “ k := 1”<br />

antes del renglón del inciso anterior. Compilar y ejecutar nuevamente<br />

el programa, observando que ahora no hay inconvenientes.


7.1. Funciones Pág. 69<br />

iii) Repetir los pasos anteriores cambiando k por z, comprobando que z<br />

“vive” sólodentrodelafunción.<br />

b) Observar que al declarar la función, los argumentos a y b se separan con<br />

“ ; ”, pero que al llamar la función se invoca con “ , ” separando los argumentos:<br />

“ potencia(x,n)”.<br />

i) Cambiar “ ; ”por“, ” en la declaración de la función, i.e. poner<br />

function potencia(a: real, b: integer): real;<br />

y comprobar que el compilador no acepta el cambio.<br />

ii) De modo similar, cambiar “ xn := potencia(x,n);” por<br />

xn := potencia(x;n);<br />

y comprobar que este cambio tampoco es aceptado.<br />

✎ Sin embargo, si dos o más argumentos consecutivos son del mismo<br />

tipo, podemos separarlos con una coma juntando las declaraciones.<br />

Para ejemplificar, si a y b son de tipo “ real ”, podemos declarar tanto<br />

function f(a: real; b: real):...<br />

como<br />

function f(a, b: real):...<br />

Es decir, el uso es similar a las “ , ” que se usan al declarar variables<br />

con “ var ”. Veremos un ejemplo en el problema 7.7.<br />

c) Las variables que son argumentos de la función también son locales a ella,<br />

y es posible que coincidan con los nombres de otras variables del programa,<br />

llamadas globales.<br />

i) Cambiar la definición de la función a<br />

function potencia(x: real; n: integer): real;<br />

var k: integer; z: real;<br />

begin<br />

z := 1;<br />

for k := 1 to n do z := z * x;<br />

potencia := z<br />

end;<br />

y comprobar (compilando y ejecutando el programa) que el comportamiento<br />

es el mismo.<br />

ii) La localidad de los argumentos también se traduce en que podemos<br />

modificarlos dentro de la función, sin modificar otras variables con el<br />

mismo identificador del programa.<br />

Por ejemplo, cambiar la declaración de la función poniendo<br />

function potencia(x: real; n: integer): real;<br />

var z: real;<br />

begin<br />

z := 1;<br />

while (n > 0) do begin<br />

z := x * z; n := n - 1 end;<br />

potencia := z<br />

end;


Pág. 70 Funciones y Procedimientos<br />

Observar que el valor final de z es el mismo en uno u otro caso,<br />

pero el valor de la variable n (localalafunción) se va modificando<br />

con la nueva definición.<br />

Ejecutar el programa, y verificar que sin embargo, el valor de n<br />

que se imprime al final es el mismo que el ingresado inicialmente:<br />

corresponde a la variable global n, y no a la variable n local a la<br />

función.<br />

✎ Si bien podemos poner cualquier identificador a los argumentos,<br />

no deben ser los mismos que las otras variables locales a la<br />

función. Por ejemplo no debemos poner<br />

function potencia(x: real; n: integer): real;<br />

var k: integer; x: real;<br />

.<br />

d) Ya hemos mencionado que el nombre de la función debe ser asignado antes<br />

de terminar la función,peronoesunavariable:¿qué pasa si se pone<br />

function potencia(x: real; n: integer): real;<br />

begin<br />

potencia := x;<br />

while (n > 1) do begin<br />

potencia := x * potencia; n := n - 1 end<br />

end; ?<br />

e) Cambiar la función potencia de modo de que el cálculo se haga mediante la<br />

fórmula<br />

x n =exp(nln x),<br />

comprobando que se obtienen los resultados correctos. ✄<br />

7.2. El método de la bisección<br />

Supongamos que tenemos una función continua f definida sobre el intervalo<br />

[a, b] a valores reales (3) ,yquef(a) yf(b) tienen distinto signo, como en la<br />

figura 7.1. Cuando la dibujamos desde el punto (a, f(a)) hasta (b, f(b)), vemos<br />

que en algún momento cruzamos el eje x, yallí encontramos una raíz de f, i.e.<br />

un valor de x tal que f(x) =0.<br />

En el método de la bisección se comienza tomando a0 = a y b0 = b, ypara<br />

i =0, 1, 2,... se va dividiendo sucesivamente en dos el intervalo [ai,bi] tomando<br />

el punto medio ci, y considerando como nuevo intervalo [ai+1,bi+1] alintervalo<br />

[ai,ci] o[ci,bi], manteniendo la propiedad que en los extremos los signos de f<br />

son opuestos (que podemos expresar como f(ai)f(bi) < 0). Se finaliza cuando<br />

se obtiene un valor de x tal que |f(x)| es suficientemente chico, |f(x)|


7.2. El método de la bisección Pág. 71<br />

a = a0<br />

c0 = a1 = a2<br />

c2<br />

c1 = b2<br />

b = b0 = b1<br />

Figura 7.1: Una función continua con distintos signos en los extremos.<br />

✎ También en este sentido, observamos que 2 10 = 1024 ≈ 10 3 y 2 20 =<br />

1048576 ≈ 10 6 , por lo que el intervalo inicial se divide aproximadamente<br />

en 1000 después de 10 iteraciones y en 1000000 = 10 6 después de 20<br />

iteraciones. Es decir, después de 10 iteraciones el intervalo mide el 0.1%<br />

del intervalo original, y después de 20 iteraciones mide el 0.0001 % del intervalo<br />

original: no tiene mucho sentido poner mucho más de 10 iteraciones.<br />

Problema 7.3 (Método de la bisección para encontrar raíces). El programa<br />

biseccion (pág. 189) utiliza el método de bisección para encontrar raíces<br />

en el caso particular<br />

f(x) =x(x +1)(x +2)(x − 4/3).<br />

a) Observar la declaración de la función f en Pascal, y la estructura del cuerpo<br />

principal:<br />

1. Carteles iniciales.<br />

2. Entrada de los extremos del intervalo inicial: la cota inferior se llama<br />

poco ylasuperiormucho.<br />

3. En la inicialización, se calculan los valores de f en ambos extremos,<br />

llamados ypoco y ymucho, y se inicializa a 0 el contador de iteraciones,<br />

iter.<br />

Dado que se sigue iterando si la función toma signos distintos en los<br />

extremos, se define la variable lógica seguir que indica si la condición<br />

es cierta o no.<br />

4. En el lazo principal:<br />

• Se incrementa el contador iter, se calcula el punto medio del intervalo,<br />

medio, yelvalordefen este punto, ymedio.<br />

• Si el valor absoluto de ymedio es suficientemente chico, paramos.<br />

Esto se indica poniendo seguir en falso.<br />

• También paramos si ya hemos llegado al máximo de iteraciones.<br />

• Si no, calculamos el nuevo intervalo, cuidando de que los signos en<br />

losextremosseandistintos.<br />

Observar que sólo necesitamos el signo de ypoco para determinar<br />

el nuevo intervalo, de modo que no hemos conservado el valor de<br />

ymucho.


Pág. 72 Funciones y Procedimientos<br />

5. En la salida tenemos en cuenta las distintas posibilidades por las cuales<br />

seguir es falsa:<br />

• Si la condición de distinto signo en los extremos no se satisfacía<br />

inicialmente, no se han realizado iteraciones (por lo que el valor de<br />

iter es 0), y ponemos un cartel apropiado.<br />

• En otro caso señalamos los valores obtenidos, poniendo un cartel<br />

especial si el error no es suficientemente pequeño (y por lo tanto el<br />

número de iteraciones es máximo).<br />

6. Terminamos poniendo un cartel de “Fin”.<br />

b) Dada la forma de f, en este caso conocemos exactamente las raíces. Bosquejar<br />

el gráfico de f en el intervalo [−3, 2], y dar valores iniciales de poco<br />

y mucho para obtener aproximaciones a cada una de ellas ejecutando el<br />

programa.<br />

c) En caso de que haya más de una raíz en el intervalo inicial, la solución elegida<br />

depende de los datos iniciales. Verificar este comportamiento ejecutando<br />

el programa sucesivamente con los valores .8, 1 y 1.2 paramucho, pero<br />

tomando poco = −3 en todos estos casos.<br />

d) ¿Por quésiponemos poco = −3 ymucho =1obtenemoslaraíz x = −1 en<br />

una iteración?<br />

✎ En general, nunca obtendremos el valor exacto de la raíz: recordar que<br />

en la computadora sólo existen racionales (¡y pocos!).<br />

e) x =0esraíz, pero ¿qué pasa si ponemos poco =0ymucho =1?Modificar<br />

el programa de modo que si f(poco) of(mucho) sean en valor absoluto<br />

suficientemente pequeños, entonces se imprima la correspondiente solución<br />

y no se realice el lazo de bisección.<br />

f ) Agregar también la impresión de carteles apropiados cuando f(poco) ×<br />

f(mucho) ≥ 0ynoseestáenlas condiciones del inciso anterior.<br />

g) El programa no verifica si poco < mucho, ypodría suceder que poco ><br />

mucho. ¿Tieneestoimportancia?<br />

h) Teniendo en cuenta las notas al principio de la sección, ¿tendría sentido<br />

agregar al programa un criterio de modo de parar si los extremos del intervalo<br />

están suficientemente cerca? Si la nueva tolerancia fuera εx, ¿cuántas<br />

iteraciones deben realizarse para alcanzarla, en términos de εx ylosvalores<br />

originales de mucho y poco?<br />

i) Modificar el programa de modo que en vez de considerar hasta un máximo<br />

de iteraciones, el programa termine cuando o bien se ha encontrado x tal que<br />

|f(x)|


7.2. El método de la bisección Pág. 73<br />

a) Resolver aproximadamente las ecuaciones:<br />

i) x 2 − 5x +2=0, ii) x 3 − x 2 − 2x +2=0.<br />

Resolver también estas ecuaciones en forma exacta y comparar con los resultados<br />

obtenidos.<br />

✎ La primera ecuación tiene 2 raíces y la segunda 3.<br />

b) Encontrar una solución aproximada de cos x = x y comparar con los resultados<br />

del problema 5.7.<br />

c) Obtener una solución aproximada de cada una de las ecuaciones<br />

2 − ln x = x y x 3 sen x +1=0.<br />

✎ La primera ecuación tiene una raíz, y la segunda tiene infinitas. ✄<br />

Problema 7.5 (Interés sobre saldo). Consideremos el siguiente problema:<br />

Un artículo cuesta $100 de lista. Puedo comprarlo al contado con un<br />

10 % de descuento, o con un plan de pagos de anticipo y 9 cuotas<br />

mensuales de $10 c/u, ¿cuál es la tasa de interés (nominal anual)<br />

que están cobrando?<br />

Resulta que hay muchas formas de calcularla (4) . En este problema estudiamos<br />

el llamado interés sobre saldo, que da como respuesta a la pregunta anterior<br />

una tasa de 29.07 %.<br />

Supongamos que pedimos prestada una cantidad c (en $) con un tasa de<br />

interés anual (nominal) r (en %), que pagaremos en cuotas mensuales fijas de p<br />

(en $) comenzando al final del primer mes, y que el préstamo tiene las característica<br />

de que el interés se considera sobre el saldo, esto es, si bm es el saldo<br />

adeudado a fin del mes m, justo antes de pagar la cuota m-ésima, y cm es el<br />

saldo inmediatamente después de pagar esa cuota, poniendo t =1+r/(100×12),<br />

tendremos:<br />

y en general<br />

b1 = tc, c1 = b1 − p, b2 = tc1, c2 = b2 − p,...<br />

cm = bm−1 − p = tcm−1 − p, (7.1)<br />

donde inclusive podríamos poner c0 = c.<br />

a) Programar una función saldo(c, r, p, m) que dados el monto inicial c, latasa<br />

r, y el pago mensual p, calcule el saldo inmediatamente después de pagar la<br />

m-ésima cuota, cm = saldo(c, r, p, m) param =1, 2,... Aclaración: nose<br />

pide encontrar una fórmula, sólo escribir la función Pascal.<br />

b) Verificar la corrección de la función anterior (dentro de un programa),<br />

usando los datos de la pregunta al principio del problema (se financian<br />

$90 − $10 = $80 en 9 cuotas de $10, a una tasa de 29.07 %; el saldo correspondiente<br />

debe ser aproximadamente 0).<br />

c) Considerando que c y r están fijos, ¿existe un valor de p de modo que el<br />

saldo sea siempre el mismo, i.e. cm+1 = cm para m =1, 2,...?, ¿cuál?<br />

(4) Yelcontadordirá“¿cuánto querés que te de?”


Pág. 74 Funciones y Procedimientos<br />

d) Para c, r y p fijos, la función saldo(c, r, p, m) esdecreciente con m si p es<br />

mayor que el monto calculado en el inciso c). ¿Cómo es el comportamiento<br />

cuando sólo varía c?, ¿y si sólo varía r?<br />

e) Hacer un programa de modo que dados r, c y p (p mayor que el monto en c))<br />

calcule el número total de cuotas n. Aclaración: todas las cuotas deben ser<br />

iguales, excepto tal vez la última que puede ser menor.<br />

f ) Hacer un programa que usando el método de bisección, y dados c, r y<br />

el número total de cuotas n, calculepde modo que la deuda se cancele<br />

exactamente con n cuotas fijas, es decir cn =0.¿Podríamos usar poco =0<br />

y mucho = a?<br />

g) El resultado anterior, p, en general no podrá pagarse en pesos y centavos<br />

(habrá más de dos decimales). Si los pagos se harán en pesos y centavos,<br />

¿qué tolerancias en p pondremos? Modificar (si es necesario) el programa<br />

anterior para incorporar esta tolerancia.<br />

h) Modificar el programa para que dados r (latasaquemecobran),p (la cuota<br />

que estoy dispuesto a pagar) y n (la cantidad de meses), calcule el monto c<br />

(al que podría acceder).<br />

✎ En calculadoras financieras y “planillas de cálculo” están implementadas<br />

funciones similares.<br />

Por ejemplo, en (la versión inglesa de) Excel están las funciones:<br />

PMT: Para calcular p dados n, r y c: “PMT(r/12,n,c) ”.<br />

PV: Para calcular c dados n, r y p: “PV(r/12,n,p) ”(pnega tivo pues es un pago).<br />

RATE: Para calcular r dados n, c y p: “RATE(n,p,c)*12 ”(p<br />

negativo pues es un pago).<br />

NPER: Calcular n dados r, a y p: “NPER(r/12,p,c) ”(pne gativo pues es un pago).<br />

Así, para calcular el pago mensual si r =8%,n =10yc = $10000,<br />

ponemos “ PMT(8 %/12,10,10000) ” para obtener −1037.03. ✄<br />

7.3. Procedimientos<br />

Los procedimientos y funciones son muy parecidos entre sí, y a veces se<br />

los engloba bajo el nombre común de rutinas (5) . De hecho, en lenguajes como<br />

“C” no hay distinción entre ellos. En Pascal, la diferencia más obvia es que los<br />

procedimientos no tienen “un resultado” visible.<br />

Pascal nos permite mezclar funciones y procedimientos, con la única restricción<br />

de que se deben declarar en el orden en que son usados: una función o<br />

procedimiento no puede llamar a una función o procedimiento que no ha sido<br />

aún declarada.<br />

✎ Una posibilidad intermedia es el uso de “ forward ”, que no veremos.<br />

Más aún, como veremos en el problema 7.6.d) y en el problema 7.8.b), es posible<br />

poner una funciónoprocedimientodentrodeotrafunción o procedimiento,<br />

yaquéllos serán locales aéstos (desconocidos para otras partes del programa).<br />

Esta acción se llama anidamiento de funciones o procedimientos.<br />

(5) Aunque en algunos lenguajes, ¡las rutinas son nuestros procedimientos!


7.3. Procedimientos Pág. 75<br />

Podemos pensar, recordando la figura 2.4, que al alojarse en la memoria el<br />

programa ejecutable, hay un espacio reservado para funciones y procedimientos,<br />

como se indica en la figura 7.2. A su vez, funciones y programas pueden repetir<br />

el esquema si hay anidamiento.<br />

programa<br />

ejecutable<br />

datos<br />

globales<br />

funciones,<br />

procedimientos<br />

instrucciones<br />

(parte<br />

principal)<br />

datos<br />

locales<br />

instrucciones<br />

locales<br />

datos<br />

locales<br />

instrucciones<br />

locales<br />

Figura 7.2: Esquema de programa, funciones y procedimientos en la memoria.<br />

Volvamos al problema 4.12 (pág. 32) donde hicimos una tabla del seno usando<br />

el programa tablaseno (pág. 177). Podemos esquematizar ese programa por<br />

medio de los siguientes pasos:<br />

1. Poner carteles.<br />

2. Leer los datos, en este caso inicial, final e incremento.<br />

3. Hacer e imprimir la tabla.<br />

4. Señalar el fin del programa.<br />

Pascal nos permite poner cada uno de estos pasos como procedimiento, poniendo<br />

en el cuerpo principal del programa (6) :<br />

begin<br />

cartelesiniciales;<br />

leerdatos;<br />

imprimirtabla;<br />

cartelfinal<br />

end.<br />

donde cartelesiniciales, leerdatos, imprimirtabla y cartelfinal son procedimientos<br />

que realizarán las acciones correspondientes.<br />

La ventaja de hacerlo es que podemos preocuparnos por cada uno de los<br />

procedimientos —y si las hubiera, funciones— por separado. Así, podríamos<br />

definir el procedimiento cartelesiniciales como<br />

procedure cartelesiniciales;<br />

begin<br />

writeln(’Hacer una tabla del seno dando valores’);<br />

writeln(’inicial, final, y del incremento (en grados).’);<br />

writeln<br />

end;<br />

(6) Claro que es una exageración. Lo hacemos sólo a modo de ejemplo.


Pág. 76 Funciones y Procedimientos<br />

Problema 7.6. En el programa tablaseno2 (pág. 191) hemos reescrito el programa<br />

tablaseno, con las modificaciones mencionadas.<br />

a) Comparar ambos programas, observando cómo se han declarado los procedimientos<br />

en tablaseno2 ycómo se corresponden con las sentencias de<br />

tablaseno.<br />

Observar que las variables inicial , final e incremento se han declarado<br />

como globales, puesto que se usan en distintas partes, mientras que grados<br />

y radianes son locales al procedimiento imprimirtabla, pueseselúnico que<br />

las usa.<br />

b) Compilar y ejecutar tablaseno2 verificando su comportamiento.<br />

c) En el problema 4.12 nos preocupamos por cómo dar un valor aproximado de<br />

π, teniendo las alternativas de definirlo “manualmente” o usar una fórmula<br />

como π =4× arctan 1, a la que podríamos agregar el uso de la técnica de<br />

punto fijo del problema 5.8.<br />

Eliminar la declaración de pi como constante en el programa tablaseno2,<br />

declararlo en cambio como variable real e incluir el procedimiento:<br />

procedure calculodepi;<br />

begin<br />

pi := 4 * arctan(1)<br />

end;<br />

como primer procedimiento, incluyendo la sentencia “ calculodepi” enel<br />

cuerpo principal del programa. Compilar y ejecutar el programa con estas<br />

modificaciones.<br />

d) En realidad, el valor de pi se usa sólo para pasar de grados a radianes. Eliminar<br />

las declaraciones de pi, calculodepi ylasentencia“calculodepi” del<br />

programa, y en cambio colocarlas dentro del procedimiento imprimirtabla.<br />

Debería quedar algo como:<br />

procedure imprimirtabla;<br />

var<br />

grados: integer;<br />

radianes, pi: real;<br />

procedure calculodepi;<br />

begin pi := 4 * arctan(1) end;<br />

begin<br />

calculodepi;<br />

writeln(’Angulo Seno’);<br />

grados := inicial;<br />

while (grados


7.4. Pasando por valor o por referencia Pág. 77<br />

7.4. Pasando por valor o por referencia<br />

Tanto los procedimientos como las funciones tienen en general uno o más<br />

parámetros que son los argumentos para evaluarlos (7) . Tenemos que distinguir<br />

entre los parámetros que están en la descripción de la función o procedimiento,<br />

llamados parámetros formales y los que se especifican en cada llamada de la<br />

función o procedimiento, llamados parámetros reales.<br />

Los parámetros formales se utilizan solamente dentro del cuerpo de la función<br />

o procedimiento y son locales a él, es decir, son desconocidos fuera de la<br />

función o procedimiento. Por supuesto, los parámetros reales deben tener el<br />

mismo tipo que los formales. De alguna forma, podemos pensar que la función<br />

o procedimiento tiene sus propias “cajas” (los parámetros formales) en las que<br />

alojar las variables que son pasadas (los parámetros reales).<br />

Por ejemplo, volviendo al programa potencias3 (problema 7.2), recordemos<br />

que la función potencia se declaraba como<br />

function potencia(a: real; b: integer):...<br />

En este caso, los parámetros a y b son los parámetros formales, mientras que<br />

al hacer la llamada<br />

xn := potencia(x,n)<br />

x y n se convierten en los parámetros reales. Los valores de x y n —“cajas”<br />

globales del programa— se copian (respectivamente) en las “cajas” locales a y<br />

b.<br />

Pero no siempre esta “copia de valores” es adecuada:<br />

Problema 7.7 (Intercambio de variables). Supongamos que queremos intercambiar<br />

los valores de las variables u y v, es decir poner en u lo que está en<br />

v yrecíprocamente. No podemos poner sencillamente “ u := v; v := u” pues<br />

quedaría u = v (8) . La forma usual de hacer el intercambio es usando una variable<br />

intermedia o temporal t (del mismo tipo que u y v), siguiendo un esquema<br />

como “ t := u; u := v; v := t”, como se indica en la figura 7.3.<br />

2<br />

u v<br />

1 3<br />

t<br />

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

Los números indican el orden de los pasos a realizar.<br />

Si queremos implementar este intercambio como procedimiento, el primer<br />

impulso es poner (suponiendo variables enteras) un procedimiento como en el<br />

programa intercambio (pág. 192).<br />

✎ Observar la declaración de los argumentos en incorrecto: cuando hay dos<br />

(o más) consecutivos del mismo tipo, en este caso “ integer ”, podemos<br />

separarlos por comas poniendo “ a, b: integer ”, en vez del equivalente<br />

a“a: integer; b: integer ”.<br />

(7) Pero podrían no tenerlos, como en el procedimiento carteles del programa tablaseno2.<br />

(8) Ante la duda, recomendamos hacer “pruebas de escritorio” en este problema.


Pág. 78 Funciones y Procedimientos<br />

a) Ejecutar el programa, y comprobar que los valores de x y y no han cambiado.<br />

El mecanismo de copiar los valores de x, y en las cajas locales a, b es la que<br />

produce el resultado no deseado. Por suerte, existe un mecanismo que al llamar<br />

a la función o procedimiento hace equivalentes las variables correspondientes.<br />

En Pascal, esto se hace incorporando la palabra “ var ” a las variables deseadas:<br />

b) Cambiar el procedimiento incorrecto del programa anterior por el siguiente:<br />

procedure correcto(var a, b: integer);<br />

(* a, b pasados por referencia *)<br />

var t: integer;<br />

begin t := a; a := b; b := t end;<br />

Probarlo y comprobar que los valores finales de x y y son ahora los correctos.<br />

✄<br />

Como hemos visto en el problema anterior, en funciones y procedimientos<br />

podemos indicar si queremos trabajar con una copia de los parámetros reales,<br />

o directamente con éstos. Este mecanismo se llama de “sustitución” y es usual<br />

distinguir las siguientes clases de sustitución de parámetros:<br />

Sustitución por valor: El parámetro real se evalúa, y el valor resultante<br />

sustituye al correspondiente parámetro formal.<br />

Sustitución por referencia: El parámetro real es una variable; los posibles<br />

índices se evalúan, y la variable así identificada sustituye a su<br />

correspondiente parámetro formal. Es utilizada si el parámetro representa<br />

el resultado de un procedimiento.<br />

Por ejemplo, dado el fragmento<br />

var i: integer; a: array[1..2] of integer;<br />

procedure P(x: integer);<br />

begin i := i + 1; x := x + 2 end;<br />

begin (* cuerpo principal *)<br />

a[1] := 10; a[2] := 20; i := 1; P(a[i])<br />

end.<br />

nos preguntamos cuáles son los valores finales del arreglo a.<br />

En este caso, tenemos la sustitución por valor: lavariablexen el procedimiento<br />

P tiene valor inicial 10 = a1. El valor final de a es (10, 20).<br />

Si cambiamos la definición del procedimiento P a<br />

procedure P(var x: integer);<br />

begin i := i + 1; x := x + 2 end;<br />

tendremos la sustitución por referencia: en el procedimiento P , x = a1 yla<br />

sentencia “ x := x + 2” significa “ a[1] := a[1] + 2 ”. Por lo tanto, el valor<br />

final de a es (12, 20).<br />

Recordar entonces que<br />

• En Pascal la sustitución por valor es la usual, si queremos sustitución por<br />

referencia anteponemos la palabra “ var ”alparámetro formal.<br />

• No podemos poner como parámetro real una constante cuando el parámetro<br />

se pasa por referencia. Por ejemplo las declaraciones:


7.4. Pasando por valor o por referencia Pág. 79<br />

.<br />

procedure P(var x: integer):<br />

.<br />

begin (* parte principal *)<br />

.<br />

P(1);<br />

.<br />

.<br />

end.<br />

son incorrectas.<br />

• Si hay varios parámetros, algunos pueden pasarse por valor y otros por<br />

referencia (con “ var ”). Si hay una lista de parámetros separados por<br />

“ , ”, como en “ proc(var a, b: integer; c, d: integer)”, entonces<br />

los parámetros a y b se pasan por referencia mientras que los parámetros<br />

c y d se pasan por valor (aunque es conveniente evitar estas confusiones,<br />

poniendo explícitamente la palabra “ var ” delante de cada parámetro que<br />

se pasará por referencia).<br />

Problema 7.8. En los siguientes programas determinar los valores de los parámetros<br />

de las sentencias “ writeln ” y luego comprobar las respuestas ejecutándolos<br />

(agregando encabezado):<br />

a) var a, b, c: integer;<br />

procedure P(x, y: integer; var z: integer);<br />

begin z := x + y + z; writeln(x, y, z) end;<br />

begin a:= 5; b := 8; c := 3;<br />

P(a, b, c); P(7, a + b + c, a); P( a * b, a div b, c)<br />

end.<br />

b) var i, j, k: integer;<br />

procedure P(var i: integer);<br />

begin i := i + 1; writeln(i, j, k) end;<br />

procedure Q( h: integer; var j: integer);<br />

var i: integer;<br />

procedure R;<br />

begin i := i + 1 end;<br />

begin i := j;<br />

if (h = 0) then P(j) else if h = 1 then P(i) else R;<br />

writeln( i, j, k)<br />

end;<br />

begin i := 0; j := 1; k := 2; Q(0,k); Q(1,i); Q(2,j)<br />

end.


Pág. 80 Funciones y Procedimientos<br />

✎ Observar que el procedimiento “ R ” es local al procedimiento “ Q ”, y desconocido<br />

globalmente. Recordar el problema 7.6.d). ✄<br />

7.5. Comentarios Bibliográficos<br />

La sección 7.4 —incluyendo los problemas— está basada en los libros de<br />

Wirth [15] y[16].


Capítulo 8<br />

Todos juntos: arreglos,<br />

funciones y procedimientos<br />

8.1. Definiendo nuestros propios tipos de datos:<br />

“type”<br />

Hemos visto cuatro tipos de datos elementales —“ boolean”, “ char ”, “ integer<br />

”y“real ”— pero Pascal también nos permite crear nuevos tipos mediante<br />

la palabra “ type ”. Esto es especialmente conveniente cuando trabajamos<br />

con varios arreglos de las mismas características (y como veremos más tarde,<br />

también para otras estructuras).<br />

Supongamos por ejemplo que queremos trabajar con dos arreglos, a y b,<br />

declarados como<br />

var a, b: array[1..100] of integer<br />

Puesto que tienen las mismas características, podríamos definir un tipo para<br />

estos arreglos, por ejemplo<br />

type arregloentero = array[1..100] of integer<br />

después de la declaración de constantes y antes de la de variables. Observar que<br />

usamos el signo “ = ”, como en las constantes, y no “ : ” que usamos al declarar<br />

variables.<br />

Luego podemos declarar (en la parte de “ var ”)<br />

a, b: arregloentero<br />

lo que nos permite hacer asignaciones de la forma “ a := b”, y no tener que<br />

hacer un lazo para la asignación (1) .Más importante, es altamente conveniente y<br />

recomendable usar como argumentos de funciones o procedimientos únicamente<br />

parámetros de los tipos elementales o declarados con “ type ”, a fin de evitar<br />

errores.<br />

La estructura de un programa Pascal —a la que ya no haremos modificaciones—<br />

toma entonces la forma del cuadro 8.1, y la estructura de funciones<br />

(1) No obstante, no es posible hacer la comparación “ a = b”. En este caso habrá que usar<br />

un lazo.


Pág. 82 Todos juntos: arreglos, funciones y procedimientos<br />

y procedimientos es similar (pueden tener constantes, tipos, etc. propios), sólo<br />

que terminan con “ end; ”envezde“end. ”.<br />

1. program nombre (input, output);<br />

2. const si hubiera que definir constantes.<br />

3. type si hubiera definidos tipos propios.<br />

4. var si hay variables.<br />

5. Si las hubiera, funciones (con “ function ”) y procedimientos<br />

(con “ procedure ”). Deben declararse teniendo en cuenta<br />

el orden en que serán usadas.<br />

6. begin<br />

7. Instrucciones.<br />

8. end.<br />

Cuadro 8.1: Estructura de un programa Pascal.<br />

8.2. Ingreso e impresión de arreglos<br />

Ahora que contamos con funciones, es un buen momento para repasar la<br />

entrada de datos del programa busquedalineal del problema 6.2.<br />

Problema 8.1 (Ingreso de arreglos). Supongamos que queremos ingresar<br />

un arreglo a =(a1,a2,...,an) de datos (de algún tipo), ingresando un dato<br />

por renglón, poniendo los carteles en cada paso, y terminando la entrada con<br />

“ sin datos”. Siguiendo el esquema del programa busquedalineal, e<br />

incorporando el control del número de datos, de ahora en más vamos a usar un<br />

procedimiento para englobar esta acción.<br />

Si declaramos “ const MAXN = 100; ” y los tipos<br />

tipodato = integer; (* real, char,... *);<br />

tipoarreglo = array[1..MAXN] of tipodato;<br />

podemos leer arreglos del tipo “ tipoarreglo” mediante el siguiente procedimiento:<br />

procedure leerarreglo(var a: tipoarreglo; var n: integer);<br />

var findatos: boolean;<br />

begin<br />

write(’Entrar numeros enteros,’); (* o reales o... *)<br />

writeln(’ uno por renglon y no mas de ’, MAXN:1, ’.’);<br />

writeln(’ Fin con retorno sin entrada de datos.’);<br />

n := 1; findatos := false;<br />

repeat<br />

if (n > MAXN) then findatos := true<br />

else begin<br />

write(’Entrar el dato ’, n:2);<br />

write(’ (fin = ): ’);<br />

if (eoln) then begin findatos := true; readln end<br />

else begin readln(a[n]); n := n + 1 end<br />

end


8.2. Ingreso e impresión de arreglos Pág. 83<br />

until findatos;<br />

n := n - 1<br />

end;<br />

Podemos también copiar la impresión de arreglos del mismo programa, con<br />

las modificaciones del problema 6.2:<br />

procedure escribirarreglo(a: tipoarreglo; n: integer);<br />

const maxrenglon = 10; (* maxima cantidad por renglon *)<br />

var i: integer;<br />

begin<br />

for i := 1 to n do begin<br />

write(a[i]:6); (* cambiar formato para reales,... *)<br />

if ((i mod maxrenglon) = 0) then writeln<br />

end;<br />

if ((n mod maxrenglon) > 0) then writeln<br />

end;<br />

Hacer un programa con los procedimientos leerarreglo y escribirarreglo (que<br />

acabamos de describir), para leer y luego imprimir un arreglo de enteros, de<br />

modo que el cuerpo principal sea<br />

begin<br />

writeln(’** Prueba de lectura e impresion de arreglos’);<br />

writeln;<br />

leerarreglo( arreglo, ndatos);<br />

writeln(’El arreglo leido es:’);<br />

escribirarreglo( arreglo, ndatos);<br />

writeln; writeln(’** Fin **’)<br />

end.<br />

Cuando esté funcionando correctamente, cambiar el tipo de datos ingresado<br />

de entero a real. ✄<br />

Problema 8.2. En el programa maximocaracter (pág. 192) se ingresa un arreglo<br />

de caracteres (un renglón), lo reproduce y encuentra su máximo.<br />

Observar la declaración del tipo tiporenglon con “ type ”.<br />

✎ Según el estándar Pascal, son incorrectas la declaraciones del tipo<br />

var s: array[1..10] of char<br />

como hacemos en el programa maximocaracter, ydeberían declararse como<br />

var s: packed array[1..10] of char<br />

Sin embargo, hemos preferido no adherir al estándar en este caso, para<br />

no introducir el concepto de “packed”. Si el compilador no lo admite,<br />

habrá que hacer la declaración según el estándar.<br />

Observar también que el procedimiento para leer el arreglo es ahora diferente<br />

al procedimiento leerarreglo del problema 8.1, pues no queremos entrar una letra<br />

por renglón: debemos cambiar “ readln(c)” por“read(c) ”, que lee el carácter<br />

c, pero no el fin de línea.<br />

✎ Recordando el problema 3.3.b), si pusiéramos “ readln(c) ” leeríamos c y<br />

se ignorarían los restantes caracteres hasta el fin de línea.<br />

a) max es un procedimiento y no una función, ¿por qué?


Pág. 84 Todos juntos: arreglos, funciones y procedimientos<br />

b) Observar que el argumento t en max es del tipo “ tiporenglon” y se pasa<br />

por referencia (mediante “ var ”).<br />

✎ En otro caso, habría que hacer una copia del arreglo (local a la función),<br />

ocupando lugar y tiempo. En los ejemplos con los que estamos<br />

trabajando, esto no tiene mayor importancia, debido al tamaño pequeño.<br />

c) max supone que long > 0. Dar un ejemplo donde es 0 y modificar el programa<br />

para considerar este caso.<br />

d) Modificarlo de modo de encontrar también el mínimo, usando un nuevo<br />

procedimiento.<br />

e) Modificarlo de modo que se intercambien las posiciones del máximo y el<br />

último elemento, imprimiendo el nuevo renglón. ✄<br />

Problema 8.3. Desarrollar un programa que, dado un arreglo de enteros entrado<br />

como en el problema 8.1, y sin usar otro arreglo adicional, lo invierta, i.e.<br />

si el arreglo inicialmente es (a1,a2,...,an), el arreglo final es (an,...,a2,a1).<br />

Sugerencia: hacer los intercambios a1 ↔ an, a2 ↔ an−1,. . . ✄<br />

Problema 8.4. Desarrollar un programa que leyendo un renglón (como en el<br />

programa maximocaracter), lo escriba al revés, e.g. si la entrada es<br />

“ dabale arroz a la zorra el abad ”<br />

el programa escriba<br />

“ daba le arroz al a zorra elabad ”<br />

Sugerencia: ver el problema anterior. ✄<br />

8.3. La caja de herramientas<br />

Iremos presentando cada vez menos programas completos, bajo el entendimiento<br />

de que cuestiones “comunes” ya han sido vistas, como leer o imprimir<br />

arreglos.<br />

Es una buena idea armar una “caja de herramientas” formada por archivos<br />

de texto en el disco, con las funciones o procedimientos que nos parecen más<br />

usados, o quizás más difíciles de reproducir. De esta forma, cuando necesitemos<br />

alguno, podemos simplemente hacer una copia en el programa, haciendo quizás<br />

pequeños cambios, sin necesidad de escribirlos —mucho menos pensarlos— cada<br />

vez.<br />

✎ Los sistemas de programación más avanzados incluyen en bibliotecas estas<br />

funciones o procedimientos, muchas veces en forma binaria difícil de modificar.<br />

Aunque similar, el concepto es ligeramente diferente del de la “caja<br />

de herramientas”.<br />

Por ejemplo, los procedimientos leerarreglo y escribirarreglo del problema 8.1<br />

nos dan una “plantilla” (o “template”) para leer o imprimir arreglos de números<br />

eventualmente modificando el índice inicial, e.g. “ array[1..MAXN]”, o el tipo<br />

de dato del arreglo, e.g “ real ”envezde“integer ”.


8.4. Arreglos multidimensionales Pág. 85<br />

Problema 8.5 (La Caja de Herramientas). Copiar en sendos archivos de<br />

texto (2) , los procedimientos leerarreglo y escribirarreglo del problema 8.1,ypracticar<br />

incorporarlos en un programa para leer y escribir un arreglo de enteros<br />

(como en el problema 8.1). ✄<br />

Seguramente habrá muchos procedimientos o funciones que querrás incorporar<br />

a la caja de herramientas, algunos que ya hemos visto como el método de la<br />

bisección (sección 7.3), y otros que veremos más adelante, como alguno de los<br />

métodos de búsqueda y clasificación del capítulo 10.<br />

8.4. Arreglos multidimensionales<br />

Los arreglos unidimensionales no son apropiados para guardar la información<br />

contenida en una tabla (como la del seno o del logaritmo). En estos casos<br />

es más conveniente usar un “arreglo de arreglos”, también llamado arreglo multidimensional.<br />

Por ejemplo, si queremos guardar una matriz de reales de 2 × 3, podemos<br />

pensar que necesitamos un arreglo de dimensión 2 (cantidad de filas), cuyos<br />

elementos individuales son arreglos de dimensión 3 (cantidad de columnas).<br />

Hacemos entonces la declaración<br />

m: array[1..2] of array[1..3] of real;<br />

o equivalentemente,<br />

m: array[1..2,1..3] of real;<br />

y accedemos al elemento (i, j) usando indistintamente “ m[i][j] ”o“m[i,j] ”.<br />

Las matrices son un ejemplo de estructura de dos dimensiones, pero no hay<br />

inconvenientes en considerar estructuras con cualquier número de dimensiones.<br />

Si en vez de números guardamos caracteres, las filas pueden pensarse como<br />

renglones, como hacemos en el siguiente problema.<br />

Problema 8.6. En este problema consideraremos un texto como una serie de<br />

renglones o líneas no vacías (cada una tiene al menos un carácter). Declaramos<br />

const<br />

MAXC = 255; (* maxima cantidad de caracteres por renglon *)<br />

MAXR = 20; (* maximo numero de renglones *)<br />

type<br />

tiporenglon = array[1..MAXC] of char;<br />

tipotexto = array[1..MAXR] of tiporenglon;<br />

caracteresenrenglon = array[1..MAXR] of integer;<br />

var<br />

nr, (* numero de renglones *)<br />

nc (* numero de caracteres en renglon *)<br />

: integer;<br />

texto: tipotexto;<br />

cenr: caracteresenrenglon;<br />

(2) En algunos sistemas operativos, es conveniente que tengan la extensión “ .txt ”.


Pág. 86 Todos juntos: arreglos, funciones y procedimientos<br />

eventualmente recordando la nota en el problema 8.2.<br />

a) Desarrollar un procedimiento o función para leer no más de MAXR renglones,<br />

con no más de MAXC caracteres por renglón, dando como señal de fin<br />

de entrada un “renglón vacío”.<br />

Sugerencia: copiar las ideas del programa maximocaracter.<br />

Sugerencia si la anterior no alcanza:<br />

nr := 0; (* numero de renglon leido, al ppo. ninguno *)<br />

while (not eoln) do begin<br />

(* mientras el renglon a leer no sea vacio... *)<br />

nr := nr + 1; (* hay un renglon mas *)<br />

nc := 0; (* que todavia no tiene caracteres leidos *)<br />

repeat (* leemos los caracteres *)<br />

nc := nc + 1; (* y los guardamos en el *)<br />

read(texto[nr][nc]) (* lugar correspondiente *)<br />

until (eoln); (* hasta llegar al fin del renglon *)<br />

readln; (* leemos el fin de linea *)<br />

cenr[nr] := nc (* nro. de caracteres en renglon nr *)<br />

end;<br />

b) Incorporar un procedimiento para escribir renglones, y verificar que el proceso<br />

de lectura desarrollada en el inciso anterior es correcto.<br />

✎ A veces arreglos como texto y cenr se dicen paralelos, pues la información<br />

se va actualizando simultáneamente. Cuando veamos registros, en la<br />

sección 10.4, veremos otra forma de guardar información de este tipo. ✄<br />

8.5. “Strings”<br />

Problema 8.7. El tipo “ string ”noesestándar en Pascal, pero existe en casi<br />

todos los compiladores, en particular en Turbo Pascal. Probar el comportamiento<br />

del compilador con un programa que lee un renglón entrado por terminal, vía<br />

“ readln(s) ”, donde se declara s como “ string[100]”, indicando su longitud,<br />

y/o simplemente como “ string ”.<br />

En caso de aceptar alguna de estas variantes, agregar al programa las instrucciones:<br />

a) “writeln(s)”, para escribir s,<br />

b) “length(s) ”, para averiguar su longitud, y<br />

c) “for i := 1 to length(s) do writeln( i:3, ’: ’, s[i]) ”, para escribirlo<br />

carácter por carácter, indicando el índice.<br />

d) Modificar el programa del problema 8.6, cambiando el tipo “ renglon ”por<br />

el tipo “ string ”.<br />

✎ Según el estándar Pascal, el tipo “string” es una denominación genérica<br />

para el tipo char y para arreglos empaquetados de caracteres, como los<br />

mencionados en la nota del problema 8.2. ✄<br />

8.6. Manejo elemental de archivos de texto<br />

A veces resulta útil guardar los resultados obtenidos por un programa, para<br />

después leerlos, imprimirlos o utilizarlos en otros programas. Dado el tamaño


8.6. Manejo elemental de archivos de texto Pág. 87<br />

de las “salidas” de los programas con los que trabajamos, será suficiente para<br />

nosotros trabajar con archivos de texto, es decir, archivos que podemos abrir<br />

(para leer, modificar o imprimir) con un editor de textos.<br />

El lenguaje Pascal no es especialmente apto para leer y escribir archivos, y es<br />

por ello que se han realizado muchas extensiones (que no respetan el estándar)<br />

tratando de mejorarlo, pero trataremos de ceñirnos al estándar. En él se establece<br />

otro tipo elemental de datos, además de los que ya hemos visto: el tipo<br />

“ text ”, o archivo de texto.<br />

✎ Las variables de tipo “ text ” que son argumentos de una función o procedimiento<br />

deben pasarse por referencia, i.e. anteponiendo “ var ”(versección<br />

7.4).<br />

Dos estructuras fundamentales para el manejo de archivos son: copiar de<br />

consola a archivo, y copiar de archivo a consola, que mostramos en los programas<br />

deconsolaaarchivo (pág. 194) ydearchivoaconsola (pág. 195) respectivamente, y<br />

que pasamos a comentar.<br />

• Al leer los programas, observamos que el el archivo a leer o escribir tiene<br />

una variable asignada, curiosamente llamada archivo, declarada como de<br />

tipo “ text ”. Una de las primeras cosas que hemos de hacer es relacionar<br />

este identificador, interno al programa, con el nombre que el archivo tiene<br />

o tendrá en el disco. Para eso, primeramente el usuario ingresa el nombre<br />

tal como aparece o aparecerá eneldisco.<br />

✎ Nos hemos permitido usar “ string ” para la variable correspondiente,<br />

aunque no es estándar y habrá que hacer modificaciones si el<br />

compilador no lo acepta. Recordar la nota en el problema 8.2.<br />

• Luego el nombre interno y el externo se relacionan mediante “ rewrite ”<br />

si el archivo se escribirá, o mediante “ reset ” si el archivo se leerá.<br />

Ha de tenerse cuidado con “ rewrite ”, pues si el archivo no existe, se crea<br />

uno nuevo, pero si ya existe, sus contenidos son borrados.<br />

✎ En compiladores que siguen el modelo de Turbo Pascal para entrada/salida,<br />

hay que modificar los programas, ya que no aceptan el<br />

estándar.<br />

Para escribir un archivo, hay que cambiar el renglón<br />

rewrite(archivo, nombre);<br />

por<br />

assign(archivo, nombre); rewrite(archivo);<br />

en el programa deconsolaaarchivo. Del mismo modo, para leer desde<br />

un archivo, hay que modificar el renglón<br />

reset(archivo, nombre);<br />

por<br />

assign(archivo, nombre); reset(archivo);<br />

en el programa dearchivoaconsola.<br />

Sintetizamos estas diferencias en el cuadro 8.2.


Pág. 88 Todos juntos: arreglos, funciones y procedimientos<br />

Estándar Turbo Pascal<br />

escribir rewrite(archivo, nombre) assign(archivo, nombre);<br />

archivo rewrite(archivo)<br />

leer reset(archivo, nombre) assign(archivo, nombre);<br />

archivo reset(archivo)<br />

Cuadro 8.2: Diferencias entre el estándar y Turbo Pascal para leer o escribir<br />

archivos.<br />

• Luego de leer el nombre externo y relacionarlo con el interno, debemos<br />

leer de consola e ir escribiendo el archivo en un caso, y recíprocamente,<br />

leer del archivo y escribir en consola en el otro.<br />

En el primer caso vemos una estructura similar a la lectura de renglones<br />

del problema 8.6, exceptoquealescribirnousamos“write(c)” sino<br />

“ write(archivo, c) ”paraescribircen el archivo y no la consola, debiendo<br />

incorporar el nombre del archivo. Del mismo modo, “ writeln(archivo)<br />

”(sinelargumentoc) escribeun“findelínea” terminando el<br />

renglón en el archivo.<br />

En el segundo caso, cuando leemos del archivo en dearchivoaconsola eimprimimos<br />

en la terminal, volvemos a encontrar una estructura conocida,<br />

excepto que ahora el “fin de datos” que anteriormente señalábamos con<br />

“ vacío”, ahora se señala de un modo especial para archivos de<br />

textos: el “fin de archivo”.<br />

De modo similar a “ eoln ”, “ eof(archivo)” pregunta si ya hemos llegado<br />

aestaseñal en el archivo que estamos leyendo.<br />

✎ Hay que tener cuidado pues no debe llamarse a “ eoln ”siseha<br />

llegado al final del archivo, ya que un archivo de texto puede no<br />

tener fin de línea: primero siempre debe llamarse a “ eof ”.<br />

También, “ read(c) ” se cambia por “ read(archivo,c)” para leer c desde<br />

el archivo y no la consola.<br />

• Al terminar de leer o escribir el archivo, usamos “ close(archivo)”, lo<br />

que hace que ya no se relacionen el nombre del archivo en el disco y la<br />

variable nombre, y —en el caso de estar escribiendo— coloca en el archivo<br />

la señal de “fin de archivo”.<br />

Problema 8.8 (Archivos de texto).<br />

✎ Recordar los cambios a realizar —usando “ assign ”— si se sigue el modelo<br />

de Turbo Pascal.<br />

a) Compilar y ejecutar el programa deconsoloaarchivo, y verificar su comportamiento<br />

abriendo el archivo creado con un editor de textos.<br />

✎ El “directorio” en el cual el programa creará o buscará el archivo<br />

depende de la instalación del compilador. Las posibilidades son: dar<br />

una ubicación “absoluta”, indicando el “camino” completo; o crear<br />

un archivo y encontrar dónde se instaló, a veces el mismo compilador<br />

permite determinar el directorio donde se trabajará.<br />

✎ En algunos sistemas operativos es conveniente que los archivos de texto<br />

tengan la extensión “ .txt ”.


8.7. Problemas Adicionales Pág. 89<br />

b) Del mismo modo, compilar y ejecutar el programa dearchivoaconsola.<br />

c) Tomando las partes necesarias de los programas deconsolaaarchivo y dearchivoaconsola,<br />

y tal vez del problema 8.6, hacer un programa dearchivoaarchivo<br />

para copiar un archivo en otro (nuevo) archivo, y verificar con un editor de<br />

textos que el nuevo archivo es correcto. Sugerencia: dar distintos nombres<br />

a las variables para el archivo de entrada y el de salida.<br />

✎ Ya que estamos usando “ string ”, podríamos haber leído todo un renglón<br />

en vez de carácter por carácter. Sin embargo, es posible que los “renglones”<br />

tengan más de la longitud permitida por “ string ”, usualmente 256<br />

caracteres. ✄<br />

Problema 8.9.<br />

a) Hacer una función que dado n ∈ N, determine la longitud del período de la<br />

fracción 1/n (recordar el problema 6.10, pág. 64).<br />

b) Usando la función anterior, hacer un programa para crear un archivo con<br />

las longitudes de los períodos de las fracciones 1/n, paran =1, 2,...,100,<br />

escribiendo 10 longitudes por renglón. Verificar el contenido abriendo el<br />

archivo creado con un editor de textos.<br />

c) Hacer otro programa que lea el archivo creado en el inciso anterior, y determine<br />

el promedio de los períodos. ✄<br />

8.7. Problemas Adicionales<br />

Problema 8.10. Hacer un programa que dado un arreglo de n enteros, lo<br />

rote en m posiciones hacia la izquierda, sin usar un arreglo auxiliar. E.g. si<br />

n = 10 y m = 3, el arreglo (0, 1, 2, 3, 4, 5, 6, 7, 8, 9) debe transformarse en<br />

(3, 4, 5, 6, 7, 8, 9, 0, 1, 2).<br />

Sugerencia: el problema consiste en transformar el vector de dos bloques AB<br />

en BA. Si A R es la operación de invertir el arreglo A (ver problema 8.3),<br />

(A R B R ) R = BA. Implementar la inversión como función o procedimiento. ✄<br />

Problema 8.11 (Contando palabras en texto). Al usar un editor de textos<br />

es frecuentemente necesario contar la cantidad de palabras, por ejemplo porque<br />

el informe que estamos haciendo no puede superar cierto número de palabras.<br />

Hacer un programa para leer renglones de caracteres (marcando fin de la<br />

entrada con sin otro dato) y contar el número de palabras ingresados,<br />

donde para simplificar y complicar, supondremos que:<br />

• Sólo se ingresan letras (mayúsculas o minúsculas) sin acentos ni tildes,<br />

números, espacios en blanco o ’s. O sea no se ingresan signos de<br />

puntuación ni tabulación.<br />

• Una palabra es una sucesión máxima de letras y/o números sin espacios<br />

intermedios.<br />

• Pero puede haber varios espacios en blanco consecutivos o al principio o<br />

fin de cada renglón.<br />

Por ejemplo, si ingresamos


Pág. 90 Todos juntos: arreglos, funciones y procedimientos<br />

hay 23 sapos que nacieron tristes<br />

la variable era x27<br />

menos mal que las vacas no vuelan<br />

el programa debe responder que se ingresaron 17 palabras.<br />

Modificarlo de modo de contar las palabras en un archivo de texto, donde<br />

los caracteres que no son letras, números, espacios en blanco o finales de línea<br />

son ignorados, y no hay acentos ni tildes: como en un programa fuente. ✄<br />

8.8. Comentarios Bibliográficos<br />

La descripción de tipos y archivos de textos en Pascal está basada en [7]. El<br />

problema 8.10 está tomado de [4].


Capítulo 9<br />

Números Aleatorios y<br />

Simulación<br />

Muchas veces se piensa que en matemáticas las respuestas son “siempre exactas”,<br />

olvidando que las probabilidades forman parte de ella y que son muchas<br />

las aplicaciones de esta teoría.<br />

Una de estas aplicaciones es la simulación, técnica muy usada por físicos,<br />

ingenieros y economistas cuando es difícil llegar a una fórmula que describa el<br />

sistema o proceso. Así, simulación es usada para cosas tan diversas como el<br />

estudio de las colisiones de partículas en física nuclear y el estudio de cuántos<br />

cajeros poner en el supermercado para que el tiempo de espera de los clientes<br />

en las colas no sea excesivo.<br />

La simulación mediante el uso de la computadora es tan difundida, que hay<br />

lenguajes de programación (en vez del Pascal o “C”) especialmente destinados<br />

aestepropósito.<br />

9.1. Números aleatorios<br />

Cuando en la simulación interviene el azar o la probabilidad, se usan números<br />

generados por la computadora que reciben el nombre de “aleatorios” (o, más<br />

correctamente, “seudo-aleatorios”). No es nuestra intención aquí hacer una descripción<br />

de quésonestosnúmeros o cómo se obtienen: basta con la idea intuitiva<br />

de que la computadora nos da un número en cierto rango, y cualquier número<br />

del rango tiene la misma probabilidad de ser elegido por la computadora.<br />

En general, los números aleatorios se obtienen a partir de un valor inicial o<br />

semilla (“seed” en inglés); de modo que si no se indica lo contrario —cambiando<br />

la semilla— siempre obtenemos la misma sucesión de números aleatorios. Un<br />

método eficaz para obtener “números verdaderamente aleatorios”, es cambiar<br />

la semilla de acuerdo a la hora que indica el reloj de la computadora.<br />

Lamentablemente, en el estándar Pascal no está definida una función para<br />

generar números aleatorios. Aún cuando un compilador tenga implementada<br />

una tal función, el nombre con la que se llama y aún el tipo de resultado —por<br />

ejemplo si son números reales entre 0 y 1 o enteros entre 0 y maxint— dependen<br />

también del compilador.


Pág. 92 Números Aleatorios y Simulación<br />

Dado que muchos compiladores siguen el modelo de Turbo Pascal, nosotros<br />

seguiremos la notación de este compilador para no complicar más de lo necesario,<br />

apartándonos del estándar.<br />

✎ Si no se dispone de una rutina para generar números aleatorios, es necesario<br />

instalar una propia. Sin embargo, obtener números aleatorios con<br />

la computadora no es tan sencillo como tirar dados, y hay mucha teoría<br />

matemática detrás de los “buenos” generadores: el lector interesado puede<br />

consultar el excelente libro de Knuth [11, vol.2].<br />

Una posibilidad es usar las rutinas que aparecen como ejemplo en el<br />

mismo estándar de Pascal extendido (ISO 10206) adaptado al estándar<br />

estándar de Pascal sin extensiones que es el que usamos (1) .<br />

Turbo Pascal cuenta con las funciones “ random ”y“randomize ”, y la variable<br />

randseed .“random ” sin argumentos da un número aleatorio entre 0 y 1;<br />

y cuando incluimos un argumento n ∈ N, “random(n)” daunnúmero aleatorio<br />

entero entre 0 y n−1. La semilla es la variable “ randseed ”, que puede cambiarse<br />

de acuerdo al reloj de la computadora mediante la instrucción “ randomize”.<br />

9.2. Aplicaciones<br />

Problema 9.1. El programa dado (pág. 195) hace una simulación de tirar un<br />

dado mediante números aleatorios obtenidos con la sentencia “ random ”.<br />

a) La sentencia “randomize” sirve para comenzar una nueva serie de números<br />

aleatorios. Eliminarla, comentándola, ejecutar repetidas veces el programa<br />

y comprobar que siempre se obtienen los mismos resultados (o sea no es<br />

muy al azar).<br />

✎ Salvo para programas que tienen un tiempo de ejecución grande, no<br />

debe hacerse más de una llamada a “ randomize ”.<br />

b) Modificar el programa para simular tirar una moneda con resultados “cara”<br />

o “ceca”. ✄<br />

Problema 9.2. El programa dados (pág. 196) hace una simulación para encontrar<br />

la cantidad de veces que se necesita tirar un dado hasta que aparezca un<br />

número prefijado, entrado por el usuario. Gracias a la sentencia “ randomize”, el<br />

resultado en general será distinto con cada ejecución. Observar que si el usuario<br />

entra un número menor que 1 o mayor que 6, el programa no termina nunca.<br />

a) Ejecutar el programa repetidas veces, para tener una idea de cuánto tarda<br />

en aparecer un número.<br />

b) En vez de correr varias veces el programa, modificarlo de modo de realizar<br />

n simulaciones (n entrado por el usuario), mostrando como resultado el<br />

promedio de los tiros en que tardó en aparecer el número predeterminado.<br />

Sugerencia: encerrar el lazo “ repeat ”dentrodeunlazo“for ”.<br />

c) Modificar el programa original a fin de simular que se tiran simultáneamente<br />

dos dados, y contar el número de tiros necesarios hasta obtener un resultado<br />

ingresado por el usuario (entre 2 y 12). ✄<br />

(1) Una copia del estándar en formato pdf puede encontrarse en<br />

http://www.pascal-central.com/standards.html<br />

El ejemplo en el estándar está tomado, a su vez, del artículo de Wichmann and Hill, Building<br />

a Random-Number Generator, Byte, marzo 1987, págs. 127–128.


9.2. Aplicaciones Pág. 93<br />

Problema 9.3. Tomando como base el problema anterior y el programa dados,<br />

hacer un programa que diga cuántas veces debió tirarse un dado hasta que<br />

aparecieron k seis consecutivos, donde k es ingresado por el usuario. Sugerencia:<br />

poner un contador c inicialmente en 0, y dentro de un lazo “ repeat ” (donde<br />

se hace la llamada a “ random ”) el contador se incrementa en 1 si salió unseis<br />

ysinosevuelvea0.<br />

✎ En éste y en el problema 9.2, surge la duda de si el programa terminará alguna<br />

vez, dado que existe la posibilidad de que nunca salga el número<br />

prefijado, o que nunca salga k veces consecutivas. Sin embargo, se puede<br />

demostrar matemáticamente que la probabilidad de que esto suceda es 0<br />

(suponiendo que el generador de números aleatorios sea correcto). ✄<br />

Problema 9.4. En este problema usaremos la sentencia “ random ”(sinargumentos)<br />

de Turbo Pascal que da un número aleatorio real entre 0 y 1.<br />

a) Hacer un programa que elija aleatoriamente el número 1 aproximadamente<br />

el 45 % de las veces, el número 2 el 35 % de las veces, el 3 el 15 % de las<br />

veces y el 4 el 5 % de las veces. Sugerencia: considerar las sumas parciales<br />

.45,.45 + .35,...<br />

b) Generalizar al caso en que en vez de la lista (1, 2, 3, 4) se dé una lista<br />

(a1,a2,...,an) y que en vez de las frecuencias (.45,.35,.15,.5) se dé una<br />

lista (f1,f2,...,fn), con n<br />

i=1 fi = 1. Probarlo para distintos valores y<br />

verificar que las frecuencias son similares a las deseadas. ✄<br />

Problema 9.5. El compilador de Pascal de Ana tiene la sentencia “ aleat ”,<br />

que obtiene números aleatorios r ∈ R con 0 ≤ r


Pág. 94 Números Aleatorios y Simulación<br />

elemento se repite. Sugerencia: agregar un segundo arreglo para contar las<br />

apariciones.<br />

✎ La cantidad de apariciones deberían ser muy similares, aproximadamente<br />

r/s cada uno, cuando r ≫ s. ✄<br />

Problema 9.8 (Dos con el mismo cumpleaños). Mucha gente suele sorprenderse<br />

cuando en un grupo de personas hay dos con el mismo día de cumpleaños:<br />

la probabilidad de que esto suceda es bastante más alta de lo que se<br />

cree normalmente.<br />

Supongamos que en una sala hay n (n ∈ N) personas y supongamos, para<br />

simplificar, que no hay años bisiestos (no existe el 29 de febrero), de modo que<br />

podemos numerar los posibles días de cumpleaños 1, 2,...,365.<br />

a) ¿Para quévalores de n se garantiza que haya al menos dos personas que<br />

cumplen años el mismo día? Sugerencia: recordar el principio del casillero,<br />

también conocido como del palomar o de Dirichlet<br />

✎ El principio de Dirichlet dice que si hay n + 1 objetos repartidos en n<br />

casillas, hay al menos una casilla con 2 o más objetos.<br />

b) Si la sala es un cine al cual van entrando de a una las personas, ¿cuántas<br />

personas, en promedio, entrarán hasta que dos de ellas tengan el mismo<br />

día de cumpleaños? Responder esta pregunta escribiendo un programa que<br />

genere aleatoriamente días de cumpleaños (números entre 1 y 365) hasta que<br />

haya dos que coincidan, retornando la cantidad de “personas” necesarias.<br />

Hacer varias “corridas” para tener una idea más acabada.<br />

Si en el programa se usan arreglos, ¿cuál será ladimensión, de acuerdo<br />

al inciso anterior?<br />

c) Basado en el punto anterior, si en tu curso hay 30 compañeros, ¿apostarías<br />

que hay dos que cumplen años el mismo día? ✄<br />

9.3. Problemas Adicionales<br />

Problema 9.9 (Aguja de Buffon). Una aguja fina (teóricamente un segmento)<br />

de longitud l ≤ 1, es arrojada sobre un tablero dividido por líneas paralelas<br />

donde la distancia entre dos consecutivas es 1. Mostrar, mediante un programa,<br />

que el promedio de veces que la aguja cruzará una de las líneas es aproximadamente<br />

2l/π.<br />

Georges Louis Leclerc, Conde de Buffon (1707–1788) planteó este problema<br />

en 1733, dando vigor a la teoría de probabilidad geométrica. Este problema en<br />

particular es más que nada de interés teórico e introductorio al tema, pues las<br />

aproximaciones experimentales de π no son buenas. ✄<br />

Problema 9.10 (El Show de Televisión). Un problema que causó gran<br />

revuelo hacia 1990 en Estados Unidos es el siguiente:<br />

En un show televisivo el locutor anuncia al participante que detrás<br />

de una de las tres puertas que ve hay un auto 0 km, no habiendo nada<br />

detrás de las otras dos. El locutor le pide al participante que elija<br />

una de las puertas. Sin abrir la puerta elegida por el participante, el<br />

locutor abre otra puerta mostrándole que no hay nada detrás de ésta,<br />

yledalaopción al participante de cambiar su elección (pudiendo


9.3. Problemas Adicionales Pág. 95<br />

optar entre mantener su primera elección o elegir la tercer puerta).<br />

El participante gana el auto si éste está detrás de la puerta que elige<br />

finalmente.<br />

a) Intuitivamente y sin dar una justificación rigurosa, ¿es más conveniente para<br />

el participante mantener su primera elección, cambiarla o es indiferente?<br />

b) Hacer un programa que simule el juego para tener mejor idea: i) el programa<br />

elige una de las puertas donde estará el auto (sin que nosotros sepamos<br />

el resultado); ii) nosotros hagamos una elección; iii) que en base a esta<br />

elección, el “locutor” elija una segunda puerta detrás de la cual no está el<br />

auto, al azar si hay dos posibilidades; iv) que nosotros mantengamos o<br />

cambiemos la elección; y v) que diga si ganamos o no el auto.<br />

c) Modificar el programa anterior de modo de hacer automáticamente n juegos<br />

en los que siempre elegimos una puerta al azar y luego cambiamos,<br />

retornando el número de veces que ganamos. Correrlo para n = 1000 (¡primero<br />

hacerlo para n =5paraversiestáfuncionando bien!). Comparar el<br />

resultado con la respuesta en a). ✄


Capítulo 10<br />

Búsqueda y clasificación<br />

Siempre estamos buscando algo y es mucho más fácil encontrarlo si los datos<br />

están clasificados u ordenados. No es sorpresa que búsqueda y clasificación sean<br />

temas centrales en informática y que haya una enorme cantidad de material<br />

escrito al respecto. Por ejemplo, en sus clásicos libros [11] Knuth dedica al tema<br />

todo el Volumen 3 (que por supuesto, usa material de los volúmenes anteriores).<br />

Acá hacemos una introducción al tema siguiendo, en mínima proporción, la<br />

presentacióndeWirthen[16].<br />

10.1. Búsqueda lineal con centinela<br />

Empecemos recordando lo hecho en el problema 6.2 y el programa busquedalineal<br />

en el que recorríamos “linealmente” el arreglo a =(a1,a2,...,an) buscando<br />

x (suponiendo n>0).<br />

Problema 10.1. Al buscar x en el arreglo (a1,a2,...,an), el programa busquedalineal<br />

(pág. 184) usaunlazo“repeat ”:<br />

i := 0;<br />

repeat i := i + 1; seencontro := (a[i] = x)<br />

until (seencontro or (i = n));<br />

if (seencontro) then... (* se encontro en la posicion i *)<br />

¿Con cuáles de las siguientes alternativas se podría reemplazar este lazo?:<br />

a) i := 0;<br />

repeat i := i + 1 until ((i = n) or (x = a[i]));<br />

if (x = a[i]) then... (* se encontro en la posicion i *)<br />

b) i := 1;<br />

while ((i


10.1. Búsqueda lineal con centinela Pág. 97<br />

while ((a[i] x) and (i > 1)) do i := i - 1;<br />

if (x = a[i]) then... (* se encontro en la posicion i *) ✄<br />

En el lazo “ while ”o“repeat ” de los esquemas en el problema anterior para<br />

búsqueda lineal se hacen dos comparaciones, pero podemos mejorarlo haciendo<br />

una sola colocando a x como “centinela” en alguna posición de modo que siempre<br />

lo encontremos.<br />

Por ejemplo, una modificación del esquema del inciso d) del problema anterior<br />

—que es correcto— suponiendo que el arreglo a está dimensionado adecuadamente,<br />

es:<br />

(* a debe ser declarado de modo de admitir a[0] *)<br />

a[0] := x; i := n;<br />

while (x a[i]) do i := i - 1;<br />

if (i > 0) then... (* se encontro en la posicion i *)<br />

Observamos que siempre termina, ya sea porque x es un elemento del arreglo<br />

original, en cuyo caso i es el lugar que ocupa, o bien porque no se encontró, en<br />

cuyo caso i =0.<br />

✎ No hay nada misterioso en ir desde atrás hacia adelante, podríamos haber<br />

puesto el “centinela” en la posición n+1 y recorrer de adelante hacia atrás.<br />

Problema 10.2 (Búsqueda lineal con centinela). Hacer una implementación<br />

con ambas variantes (con y sin centinela) como procedimientos, incluyendo<br />

un contador para la cantidad de comparaciones en cada una, y probar el comportamiento<br />

en distintos ejemplos (entrar el arreglo como en el procedimiento<br />

leerarreglo, pág. 82, o generar uno aleatoriamente). ✄<br />

Problema 10.3. En este problema queremos hacer un procedimiento para eliminar<br />

elementos repetidos del arreglo de enteros a = (a1,a2,...,an), como<br />

en el problema 6.4, pero cuando a está ordenado de menor a mayor, e.g. si<br />

a =(1, 2, 2, 5, 6, 6, 9), queremos que al fin del procedimiento sea a =(1, 2, 5, 6, 9).<br />

En este caso podemos poner algo como<br />

procedure purgarordenado(var a: arreglo; var n: integer);<br />

(* sacar elementos repetidos del arreglo<br />

ordenado a de longitud n. *)<br />

var i, m: integer;<br />

begin<br />

m := 1; (* a[1],...,a[m] son los elementos sin repetir *)<br />

for i := 2 to n do<br />

(* incluir a[i] si no es a[m] *)<br />

if (a[m] < a[i]) then begin<br />

m := m + 1; a[m] := a[i] end;<br />

n := m<br />

end;<br />

Hacer un programa para verificar el comportamiento de este procedimiento.<br />

✄<br />

Problema 10.4 (Fusión o mezcla de arreglos ordenados). Supongamos<br />

que tenemos dos arreglos a = (a1,a2,...,an) yb = (b1,b2,...,bm) ordenados<br />

de menor a mayor (con n, m ≥ 1), y queremos construir un arreglo c =


Pág. 98 Búsqueda y clasificación<br />

(c1,c2,...,ck) queestétambién ordenado y de modo que tenga los elementos<br />

de a y b. Por ejemplo, si a = (1, 2, 5, 5) y b =(2, 3, 4), deberá serc =<br />

(1, 2, 2, 3, 4, 5, 5).<br />

✎ Recordar el problema 6.3.<br />

Hacer un procedimiento para obtener c, usando un esquema como<br />

i := 1; j := 1; k := 0;<br />

while ((i


10.2. Búsqueda binaria Pág. 99<br />

i) Verificar que (10.1) implica (10.2).<br />

ii) Verificar que si en cambio, mucho ≥ poco +2, entonces poco < medio <<br />

mucho.<br />

c) Si se cambiara el esquema del inciso a) a<br />

poco := 1; mucho := n;<br />

while (poco + 1 < mucho) do begin<br />

medio := (poco + mucho) div 2;<br />

if (a[medio] < x) then poco := medio<br />

else mucho := medio<br />

end;<br />

(* a continuación comparar x<br />

con a[mucho] y con a[poco] *)<br />

¿sería ahora correcto?<br />

d) Si en vez de considerar poco + 1 en la condición del lazo del inciso anterior,<br />

lo incorporamos a la asignación, llegamos a:<br />

poco := 1; mucho := n;<br />

while (poco < mucho) do begin<br />

medio := (poco + mucho) div 2;<br />

if (a[medio] < x) then poco := medio + 1<br />

else mucho := medio<br />

end<br />

(* a continuación comparar x con a[mucho] *)<br />

En este inciso veremos que el nuevo esquema es correcto:<br />

i) En todo momento poco ≤ mucho, la distancia entre ellos se acorta en<br />

cada “vuelta” del lazo, y al terminar es poco = mucho (y no puede ser<br />

poco > mucho) (recordar el inciso b.ii)).<br />

ii) Si x ≤ a1, poco queda fijo, y al final se compara x con a1.<br />

iii) Si x>an, mucho queda fijo en las siguientes iteraciones, y al final se<br />

compara x con an (y obtendremos que x/∈ a).<br />

iv) Si apoco


Pág. 100 Búsqueda y clasificación<br />

until ((a[k] = x) or (i >= j))<br />

b) i := 1; j := n;<br />

repeat<br />

k := (i + j) div 2;<br />

if (x = j)<br />

d) Muchos autores presentan un algoritmo un poco más ineficiente que el del<br />

problema 10.5.d), comparando x con amedio en cada paso de modo que hay<br />

dos comparaciones por “vuelta” de lazo:<br />

poco := 1; mucho := n; encontrado := false;<br />

while ((poco a[medio]) then poco := medio + 1<br />

else mucho := medio - 1<br />

end;<br />

Ver que el esquema es correcto. ✄<br />

Problema 10.7. Se ha roto un cable maestro de electricidad en algún punto<br />

de su recorrido subterráneo de 50 cuadras. La compañía local de electricidad<br />

puede hacer un pozo en cualquier lugar para comprobar si hasta allí elcable<br />

está sano, y bastará con detectar el lugar de la falla con una precisión de 5m.<br />

Por supuesto, una posibilidad es ir haciendo pozos cada 5m, pero el encargado<br />

no está muy entusiasmado con la idea de hacer tantos pozos, porque hacer<br />

(y después tapar) los pozos cuesta tiempo y dinero, y los vecinos siempre se<br />

quejan por el tránsito, que no tienen luz, etc.<br />

¿Qué lepodrías sugerir al encargado? ✄<br />

Problema 10.8 (El regalo en las cajas). Propongamos el siguiente juego:<br />

Se esconde un “regalo” en una de diez cajas alineadas de izquierda<br />

a derecha, y nos dan cuatro oportunidades para acertar. Después de<br />

cada intento nuestro, nos dicen si ganamos o si el regalo está hacia<br />

la derecha o izquierda.<br />

a) Ver que siempre se puede ganar si nos dan cuatro oportunidades.<br />

b) Simular este juego en la computadora: la computadora elige aleatoriamente<br />

una ubicación para el regalo, y luego en cada elección del usuario el programa<br />

responde si el regalo está en la caja elegida (y termina), a la derecha o a la<br />

izquierda, orientando la búsqueda. Si después de cuatro oportunidades no<br />

se acertó la ubicación, el programa termina dando el número de caja donde<br />

estaba el regalo.


10.3. Métodos elementales de clasificación Pág. 101<br />

c) Cambiar el programa de cuatro a tres oportunidades, y ver que no siempre<br />

se puede ganar.<br />

d) ¿Cuántas oportunidades habrá que dar para n cajas, suponiendo una estrategia<br />

de búsqueda binaria? Sugerencia: la respuesta involucra las funciones<br />

techo y log2. ✄<br />

10.3. Métodos elementales de clasificación<br />

Nuestra próxima tarea consistirá en ver cómo clasificar u ordenar un arreglo<br />

a =(a1,a2,...,an) de elementos no necesariamente distintos, poniendo el<br />

resultado en el mismo arreglo a —tratando de evitar usar otro arreglo—, pero<br />

con sus elementos ordenados de menor a mayor. Para ello veremos tres métodos<br />

elementales para clasificar que podemos relacionar con la forma con que<br />

generalmente cada uno ordena las cartas de un juego:<br />

Inserción directa: Levantamos una carta, buscamos su posición de derecha<br />

a izquierda en el abanico que tenemos en la mano, la colocamos en<br />

el lugar correspondiente y continuamos levantando la próxima carta.<br />

Podemos escribir el algoritmo resultante como:<br />

for i := 2 to n do begin<br />

x := a[i]; a[0] := x; j := i;<br />

while (x < a[j-1]) do begin<br />

a[j] := a[j-1]; j := j-1<br />

end;<br />

a[j] := x<br />

end<br />

✎ Observar el uso de centinela: a debe ser declarado de modo de<br />

admitir a0.<br />

Selección directa: Levantamos las cartas al mismo tiempo (todas juntas),<br />

las abrimos en abanico, y comenzando desde la izquierda buscamos la<br />

menor y la colocamos al principio, luego buscamos la segunda menor<br />

y la ubicamos en la segunda posición, etc.<br />

Acá podemosponer:<br />

for i := 1 to n-1 do begin<br />

k := i; x := a[i];<br />

for j := i+1 to n do<br />

if (x > a[j]) then begin<br />

(* nuevo maximo *)<br />

k := j; x := a[k] end;<br />

a[k] := a[i]; a[i] := x<br />

end<br />

✎ Comparar con el inciso e) delproblema8.2.<br />

Intercambio directo o burbujeo: Aquítambién levantamos las cartas al<br />

mismo tiempo y las abrimos en abanico, pero vamos mirando de derecha<br />

a izquierda buscando un par de cartas consecutivas fuera de<br />

orden y cuando lo encontramos lo invertimos. Seguimos recorriendo


Pág. 102 Búsqueda y clasificación<br />

con la mirada el abanico hasta que no haya ningún par fuera de orden.<br />

Un posible esquema es:<br />

for i := 2 to n do<br />

for j := n downto i do<br />

if (a[j-1] > a[j]) then begin<br />

(* intercambiar *)<br />

x := a[j-1]; a[j-1] := a[j]; a[j] := x<br />

end<br />

✎ Por supuesto en cada uno de los métodos podemos cambiar el sentido del<br />

recorrido, empezando desde la derecha o desde la izquierda.<br />

Problema 10.9 (Métodos elementales de clasificación). Implementar los<br />

algoritmos anteriores como procedimientos en un único programa. Agregar en<br />

cada uno contadores para el número de asignaciones y comparaciones (entre<br />

datos como x o ak ynoentreíndices como i, j o k) hechas en cada caso, y<br />

comparar estas cantidades cuando:<br />

a =(1, 2, 3, 4, 5, 6), a =(6, 5, 4, 3, 2, 1), a =(2, 6, 8, 7, 4, 5, 1, 3, 6, 4). ✄<br />

En el cuadro 10.1 mostramos una comparación entre los tres métodos y el de<br />

“clasificación por fusión” —un método avanzado que explicamos en el problema<br />

15.7— con arreglos de 10 000 enteros construidos de tres formas: ordenado,<br />

ordenado al revés, y aleatorio. Los tiempos en el cuadro son para una máquina<br />

particular (y no demasiado rápida para los estándares cuando se realizaron las<br />

pruebas) y determinado compilador, y debe considerarse sólo la proporción entre<br />

ellos (la cifra 0.00 para el tiempo indica que la fracción de segundo es menor<br />

a1/60 = 0.0166 ...). Se contaron las comparaciones y asignaciones de arreglos<br />

(y no cuando se comparan o asignan índices).<br />

Observamos en la tabla que tanto selección directa como intercambio directo<br />

realizan siempre el mismo número de comparaciones, n(n − 1)/2, y que aún<br />

cuando no se hacen asignaciones (como en el caso de intercambio directo, cuando<br />

el arreglo está ordenado), la enorme cantidad de comparaciones lleva tiempo.<br />

En cuanto a las asignaciones, hemos de destacar que hemos tomado arreglos<br />

de enteros, por lo que individualmente no llevan demasiado tiempo, y en el<br />

caso de inserción directa con un arreglo aleatorio, hay muchas asignaciones pero<br />

—comparando con los otros métodos elementales— no llevan tanto tiempo.<br />

Sería distinto si los elementos fueran arreglos, por ejemplo “strings” al clasificar<br />

palabras, o registros (que veremos un poco más adelante).<br />

Algunos autores usan búsqueda binaria en vez de búsqueda lineal en inserción<br />

directa, disminuyendo en principio el número de comparaciones. Pero las<br />

ventajas son marginales y aún pueden empeorar el algoritmo (ver [16, pág. 87]).<br />

Muchos libros de programación insisten en presentar el método de intercambio<br />

directo o burbujeo —y a veces ningún otro— que es claramente inferior a<br />

cualquiera de los otros: aún en el caso del arreglo ordenado, se hace la misma<br />

cantidad de comparaciones que selección directa, y ninguna asignación, pero ¡el<br />

tiempo es superior! Hemos incluido este método sólo para que el lector sepa<br />

que existe y que es bastante malo: nosotros casi siempre elegiremos el de selección<br />

directa o inserción directa: selección directa hace relativamente pocas<br />

asignaciones, inserción directa relativamente pocas comparaciones.


10.3. Métodos elementales de clasificación Pág. 103<br />

Arreglo ya ordenado<br />

fusión selección intercambio inserción<br />

comparaciones 71 712 49 995 000 49 995 000 9 999<br />

asignaciones 140 000 29 997 0 29 997<br />

tiempo (segs.) 0.00 1.10 1.43 0.00<br />

Arreglo ordenado al revés<br />

fusión selección intercambio inserción<br />

comparaciones 64 608 49 995 000 49 995 000 50 004 999<br />

asignaciones 140 000 25 029 997 149 985 000 50 024 997<br />

tiempo (segs.) 0.00 1.27 1.87 1.45<br />

Arreglo aleatorio<br />

fusión selección intercambio inserción<br />

comparaciones 123 582 49 995 000 49 995 000 25 023 757<br />

asignaciones 140 000 103 894 75 041 274 25 043 755<br />

tiempo (segs.) 0.00 1.10 1.75 0.72<br />

Cuadro 10.1: Comparación de algoritmos de clasificación, para arreglos enteros<br />

de longitud 10000.<br />

Problema 10.10 (Clasificación por conteo). La clasificación es muy sencilla<br />

si sabemos de antemano que el rango de los elementos de a =(a1,a2,...,an) es<br />

pequeño, digamos 1 ≤ ai ≤ m para i =1, 2,...,n.<br />

En efecto, imaginemos que tenemos n bolitas alineadas cada una con un<br />

númeroentre1ym: armamos m cajas numeradas de 1 a m, luego colocamos<br />

cadabolitaenlacajaquetienesunúmero, y finalmente será cuestión de vaciar<br />

los contenidos de las cajas ordenadamente, empezando desde la primera, alineando<br />

las bolitas a medida que las sacamos, como ilustramos en la figura 10.1.<br />

① ③ ③ ② ① ② ① ② ① ③<br />

disposición inicial<br />

①<br />

① ② ③<br />

① ② ③<br />

① ② ③<br />

1 2 3<br />

poniendo en cajas<br />

① ① ① ① ② ② ② ③ ③ ③<br />

disposición final<br />

Figura 10.1: Ordenando por conteo.<br />

En el algoritmo, en vez de “colocar cada bolita en su caja”, contamos las<br />

veces que apareció:


Pág. 104 Búsqueda y clasificación<br />

for k := 1 to m do cuenta[k] := 0;<br />

for i := 1 to n do cuenta[a[i]] := cuenta[a[i]] + 1;<br />

i := 0;<br />

for k := 1 to m do<br />

while (cuenta[k] > 0) do begin<br />

i := i + 1; a[i] := k; cuenta[k] := cuenta[k] - 1 end<br />

a) Ver que el algoritmo es correcto, i.e. al finalizar el arreglo a está ordenado,<br />

aún cuando haya valores r, 1≤ r ≤ m tales que ai = r para todo i.<br />

b) Implementar el algoritmo y comparar su eficiencia con los otros métodos,<br />

poniendo contadores para el número de asignaciones y comparaciones.<br />

c) ¿A lo sumo cuántas asignaciones de arreglos se hacen en este algoritmo (la<br />

respuesta involucra n y m)? ¿Cuándo lo usarías? ✄<br />

Problema 10.11 (Arreglos como conjuntos). Cuando queremos tratar a<br />

un arreglo a =(a1,a2,...,an) como un conjunto, es decir no nos interesan los<br />

elementos repetidos ni el orden, es conveniente primero ordenarlo (de menor a<br />

mayor) y quitar sus elementos repetidos, por ejemplo con el procedimiento del<br />

problema 10.3, para facilitar las operaciones entre conjuntos.<br />

a) Hacer un procedimiento, que a su vez usa un procedimiento de clasificar<br />

y otro de purgar, para “pasar el arreglo a a conjunto” ordenándolo<br />

y “purgándolo”.<br />

✎ Aunque se puede hacer un único procedimiento modificando un procedimiento<br />

de clasificación elemental de modo de eliminar elementos<br />

repetidos, es más sencillo, y desde el punto de vista de la modularidad<br />

más razonable, el llamar a dos procedimientos separados, uno para<br />

clasificar y otro para “purgar”. Así, se puede cambiar, por ejemplo, el<br />

procedimiento de clasificación por uno más conveniente sin tener que<br />

revisar todo el procedimiento de este inciso.<br />

Además, con métodos más avanzados es más eficiente primero clasificar<br />

y luego “purgar” que al revés.<br />

b) Dados dos arreglos a =(a1,a2,...,an) yb =(b1,b2,...,bm), considerados<br />

como conjuntos, hacer un procedimiento para construir el “conjunto intersección”,<br />

i.e. un arreglo ordenado c =(c1,c2,...,cr) con todos los elementos<br />

que están en a y b simultáneamente (pero aparecen en c una sola vez).<br />

Sugerencia: suponiendo que a =(a1,a2,...,an) yb =(b1,b2,...,bm) ya<br />

están ordenados y purgados, una posibilidad es considerar el esquema<br />

i := 1; j := 1; r := 0;<br />

repeat<br />

if (a[i] > b[j]) then j := j + 1<br />

else if (a[i] < b[j]) then i := i + 1<br />

else begin (* a[i] = b[j]: ponerlo en c *)<br />

r := r + 1;<br />

c[r] := a[i];<br />

i := i + 1;<br />

j := j + 1<br />

end<br />

until ((i > n) or (j > m))<br />

Cuando r = 0, los arreglos originales no tienen elementos comunes.


10.4. Registros (Records) Pág. 105<br />

c) Del mismo modo, hacer un procedimiento para construir el “conjunto unión”,<br />

i.e. un arreglo ordenado c =(c1,c2,...,cs) con todos los elementos<br />

que están en a o b (pero aparecen en c una sola vez).<br />

Sugerencia: podría ponerse un arreglo a continuación del otro, haciendo<br />

for i := 1 to n do c[i] := a[i];<br />

for i := 1 to m do c[i+n] := b[i];<br />

y luego “purgar” el arreglo c que tiene longitud m + n.<br />

Otra posibilidad es, si a y b ya están ordenados y clasificados, usar la<br />

“fusión” o “mezcla” del problema 10.4. ✄<br />

10.4. Registros (Records)<br />

En aplicaciones informáticas, y aún en algunas matemáticas como veremos,<br />

es conveniente agrupar la información que tenemos sobre un objeto. Por ejemplo,<br />

para una persona podríamos tener su apellido, nombres, número de identidad,<br />

etc. Una forma de hacerlo es mediante arreglos paralelos, e.g. un arreglo para<br />

los apellidos, otro para los nombres, etc., donde una persona tendría el mismo<br />

índice para cada uno de los arreglos. Otra forma más razonable es el uso de<br />

registros.<br />

Un registro es una estructura que consiste en un número fijo de componentes<br />

llamadas campos. A diferencia del arreglo, las componentes de un registro pueden<br />

ser de diferentes tipos, pero no pueden ser indexados.<br />

Al definir un tipo de registro, se especifica el tipo de cada componente y su<br />

identificador. Por ejemplo, si que queremos trabajar con números complejos de<br />

la forma a + bi,cona, b ∈ R, podemosponer:<br />

type complejo = record re, im: real end;<br />

var z: complejo;<br />

✎ Observar que la declaración del registro termina con “ end ”, pero no hay<br />

un “ begin ”.<br />

El registro ocupa un lugar de memoria donde los campos son consecutivos,<br />

de modo que podemos pensar que el complejo z = a + bi se guarda en una caja<br />

como se muestra en la figura 10.2. Enelejemplo,cada“sub-caja”ocampoes<br />

de tipo real.<br />

a b<br />

re im<br />

Figura 10.2: Esquema del registro de tipo “ complejo ”enmemoria.<br />

Para acceder a una componente del registro, el nombre del registro es seguido<br />

por un punto (“ . ”) y el correspondiente identificador del campo. Así, si<br />

quisiéramos asignar a z el valor 3 + 5 i, pondríamos<br />

z.re := 3; z.im := 5;<br />

Si z ′ es otro número complejo, podemos hacer la asignación<br />

zp := z;


Pág. 106 Búsqueda y clasificación<br />

mientras que la suma z ′′ = z + z ′ puede expresarse como:<br />

zpp.re := z.re + zp.re; zpp.im := z.im + zp.im;<br />

Las dos operaciones válidas para variables de registro<br />

(completas) son la selección de componentes y la<br />

asignación.<br />

Es importante destacar también la localidad de los nombres de los campos<br />

de un registro. La declaración<br />

var a: array[2..8] of integer;<br />

a: real;<br />

es incorrecta, pero la declaración<br />

var a: array[2..8] of integer;<br />

b: record<br />

a: real;<br />

b: boolean<br />

end;<br />

es correcta, pues “ b.a ” indica la componente “ a ”de“b ”, mientras que “ a ”<br />

indica un arreglo.<br />

Cuando trabajamos con un registro en Pascal, es posible acceder directamente<br />

a sus componentes mediante la sentencia “ with ” (en castellano, con).<br />

Por ejemplo, para asignar a z el valor 3 + 5 i, podríamos poner:<br />

with z do begin re := 3; im := 5 end;<br />

Problema 10.12. Suponiendo que declaramos el tipo “ complejo” comomás<br />

arriba:<br />

a) Hacer procedimientos para leer y escribir un complejo por terminal.<br />

b) Recordando que el producto de complejos se define como<br />

(a + bi) × (c + di)=(ac − bd)+(ad + bc) i,<br />

hacer un programa para calcular la suma y el producto de dos complejos<br />

entrados por terminal. ✄<br />

En los dos siguientes problemas, suponemos que el compilador admite el tipo<br />

“ string ”.<br />

Problema 10.13. Supongamos que queremos trabajar con un arreglo de registros,<br />

en cada uno de los cuales se guarda un nombre y un número de identificación.<br />

Declaramos<br />

const MAXN = 20;<br />

type<br />

tipoinfo = record nombre: string; nroid: integer end;<br />

tipoarreglo = array[0..MAXN] of tipoinfo;


10.4. Registros (Records) Pág. 107<br />

Una variante del procedimiento leerarreglo (capítulo 8, pág. 82), para leer<br />

datos es:<br />

procedure leerdatos(var a: tipoarreglo; var n: integer);<br />

function nuevodato: boolean;<br />

begin<br />

if (n > MAXN) then nuevodato := false<br />

else begin<br />

writeln;<br />

write(’Entrar el dato ’, n:2);<br />

writeln(’ (fin = ): ’);<br />

write(’ Entrar el nombre: ’);<br />

if (eoln) then begin readln; nuevodato := false end<br />

else begin<br />

with a[n] do begin<br />

readln(nombre);<br />

write(’ Entrar el numero de id: ’);<br />

readln(nroid)<br />

end;<br />

nuevodato := true<br />

end<br />

end<br />

end;<br />

begin<br />

writeln(’** Entrada de datos’);<br />

n := 1;<br />

while (nuevodato) do n := n + 1;<br />

n := n - 1<br />

end;<br />

Hacer un procedimiento para escribir arreglos de este tipo, y hacer un programa<br />

incorporando ambos procedimientos para probarlos. ✄<br />

Problema 10.14 (Clasificación de arreglos de registros). Supongamos<br />

que hemos hecho las declaraciones del problema 10.13, y queremos clasificar el<br />

arreglo a de tipo “ tipoarreglo”, que puede ser según nombre osegún nroid.<br />

La componente por la cual se clasifica se denomina llave o clave (en inglés<br />

“key”). Por ejemplo si tenemos el registro x con el nombre “ Geri ”yelnúmero<br />

de identidad “ 5 ”, y el registro y con el nombre “ Ana ”ynúmero “ 10 ”, ordenándolos<br />

(de menor a mayor) por nombre vendrá primero y antes que x, pero<br />

si los ordenamos por nroid vendrá primero x.<br />

✎ La comparación de strings se hace como la comparación entre números<br />

(en Pascal), e.g. “ s1 y.z ” donde z puede ser nombre o nroid según el valor de llave.


Pág. 108 Búsqueda y clasificación<br />

b) Modificar alguno de los métodos de clasificación vistos, de modo de poder<br />

clasificar un arreglo de registros, cambiando la comparación entre datos de<br />

la forma x>ypor “ mayor(x, y, llave) ”.<br />

c) Hacer un programa para ingresar el arreglo a y clasificarlo según nombre o<br />

nroid a elección del usuario, escribiendo por pantalla el resultado. ✄<br />

Problema 10.15. Queremos escribir un programa que tome como entradas<br />

caracteres (un carácter por línea y fin de entrada con sin datos),<br />

y como salida escriba los caracteres ingresados y la cantidad de apariciones,<br />

ordenados decrecientemente según la cantidad de apariciones.<br />

Por ejemplo, si la entrada son los caracteres ‘ a ’, ‘ b ’, ‘ c ’, ‘ c ’, ‘ d ’, ‘ b ’, ‘ c ’,<br />

‘ d ’, ‘ c ’, ‘ b ’, ‘ b ’, ‘ c ’, (uno por línea), la salida debe ser algo como:<br />

Caracter Cantidad de apariciones<br />

c 5<br />

b 4<br />

d 2<br />

a 1<br />

Para esta tarea vamos a construir los tipos de datos<br />

type<br />

tipoinfo = record letra: char; cuenta: integer end;<br />

tipoarreglo = array[1..MAXN] of tipoinfo;<br />

A medida que se ingresa una letra la comparamos con las letras ya ingresadas<br />

mediante búsqueda lineal (eventualmente con centinela al final). Si la letra ya<br />

apareció, se incrementa en 1 el campo cuenta correspondiente, en otro caso, se<br />

agrega al arreglo un nuevo registro con la nueva letra, poniendo cuenta =1.<br />

Luego se puede ordenar el arreglo de registros según el campo veces,enforma<br />

similar a la del problema 10.14.<br />

Hacer un programa con estas ideas. ✄<br />

10.5. Problemas Adicionales<br />

Problema 10.16. Hacer un programa para un juego como el del regalo en las<br />

cajas (problema 10.8), pero ahora jugando en un tablero de m × n. El jugador<br />

da las coordenadas (i, j), 1 ≤ i ≤ m, 1≤ j ≤ n, de la casilla que elige y la<br />

búsqueda se orienta dando direcciones hacia donde está el regalo: N, NE, E, SE,<br />

S, SO, O, NO.<br />

Recordando el problema del regalo en las cajas, ¿cuántas oportunidades<br />

habrá que dar en el caso de las m × n casillas? ✄<br />

Problema 10.17. Otra variante del juego de las cajas (problema 10.8) esla<br />

siguiente: Dado un número (entero) entre 1 y 100 (generado aleatoriamente),<br />

tratar de encontrarlo mediante “encajonamiento”, o sea vamos dando números a<br />

y b (a ≤ b) y la computadora nos va respondiendo si el número está a la izquierda<br />

de a, “atrapado” entre a y b, oaladerechadeb. Elnúmero será encontrado<br />

cuando a y b coincidan con el número a acertar, terminando el juego. Hacer un<br />

programa para este juego, y pensar una estrategia para encontrar el número en<br />

“pocas” jugadas. ✄


10.6. Comentarios Bibliográficos Pág. 109<br />

Problema 10.18 (Estabilidad para clasificación). Un método de clasificación<br />

se llama estable si el orden relativo de elementos con llaves iguales<br />

permanece inalterado en el proceso de clasificación.<br />

a) Decidir si el método de clasificación usado en el programa en el problema<br />

10.14 es estable o no, e.g. si ordenando primero por nroid ydespués<br />

por nombre, personas con el mismo nombre aparecen en orden creciente de<br />

nroid.<br />

b) Decidir cuáles de los métodos elementales de clasificación que hemos visto<br />

(inserción directa, selección directa, intercambio directo) son estables. ✄<br />

10.6. Comentarios Bibliográficos<br />

Los temas de búsqueda (con centinela y binaria) y clasificación están tomados<br />

de [16] como mencionamos. Partes de la sección 10.4 están basadas en [7,Cap.7].


Capítulo 11<br />

Recursión<br />

Teniendo en mente las sumas de Gauss (problema 4.13), supongamos que<br />

queremos encontrar todas las sumas<br />

s1 =1, s2 =1+2, s3 =1+2+3, ... sn =1+2+···+ n. (11.1)<br />

Podríamos calcular cada una de ellas separadamente, por ejemplo usando la<br />

fórmula de Gauss an = n × (n +1)/2, pero también podríamos poner<br />

s1 =1, s2 = s1 +2, s3 = s2 +3, ... sn = sn−1 + n. (11.2)<br />

Claro que no habría mucha diferencia con el esquema que vimos en el problema<br />

4.13, en el que cambiando la variable suma por un arreglo tendríamos<br />

suma[0] := 0; for i := 1 to n do suma[i] := suma[i-1] + i<br />

calculando en cada paso del lazo “ for ”lacantidadsi.<br />

Cambiando suma por producto en las ecuaciones (11.1) o(11.2), obtenemos<br />

el factorial (recordar el problema 4.14), y en realidad es usual definir n! como<br />

1! = 1 y n! =n × (n − 1)! para n ∈ N, n>1. (11.3)<br />

Esto es típico de inducción en matemáticas o de recursión en programación:<br />

dar un valor inicial y una “fórmula” para calcular los valores subsiguientes.<br />

Por ejemplo, si queremos calcular 5! usando la ecuación (11.3), tendríamos<br />

sucesivamente:<br />

5! = 5 × 4! = 5 × (4 × 3!) = 5 × 4 × (3 × 2!) =<br />

=5× 4 × 3 × (2 × 1!) = 5 × 4 × 3 × 2 × 1 = 120.<br />

En fin, ya hemos visto este esquema anteriormente, por ejemplo al calcular la<br />

función cm = saldo(c, r, p, m) enelproblema7.5 según la ecuación (7.1), donde<br />

c0 = c.<br />

11.1. Funciones y procedimientos definidos recursivamente<br />

En los problemas siguientes vamos a encontrar varios ejemplos que anteriormente<br />

resolvíamos con un lazo “ for ” o similar, ahora reescritos en forma<br />

recursiva.


11.1. Funciones y procedimientos definidos recursivamente Pág. 111<br />

Problema 11.1 (Factorial recursivo). El siguiente esquema muestra una<br />

función recursiva, i.e. que se llama a sí misma, para calcular n!, basado en la<br />

ecuación (11.3):<br />

function factorial(n: integer): integer;<br />

begin<br />

if (n > 1) then factorial := n * factorial(n - 1)<br />

else factorial := 1<br />

end;<br />

✎ Observar que la variable “sobre la que se hace la recursión”, en este caso<br />

n, no tiene antepuesta la palabra “ var ”enladefiniciónde la función. No<br />

tiene sentido hacerlo, pues cuando llamamos factorial(n−1), n−1 notiene<br />

asignado un lugar (aunque lo tenga n): n − 1 tiene que pasarse por valor y<br />

no referencia.<br />

✎ Ahora podemos apreciar un poco más por qué la función (o procedimiento)<br />

no es una variable local a la función (recordar problema 7.2.d)).<br />

a) Hacer un programa para calcular n! usando la función anterior.<br />

b) Obtener el máximo valor de n para el cual se puede calcular n! (i.e. n! ≤<br />

maxint) enlaversióndel inciso a).<br />

En vista de este resultado, es conveniente cambiar el tipo de los valores<br />

retornados por factorial de “ integer ”a“real ” en la función en a) (como<br />

hemos hecho en los problemas 4.13 y 4.14).<br />

c) Lafórmula de Stirling establece que cuando n es bastante grande,<br />

n! ≈ n n e −n√ 2πn.<br />

Hacer un programa para calcular esta aproximación, y probarla con n =<br />

10, 100, 1000. Comparar con los resultados obtenidos en a) (habiendo hecho<br />

las modificaciones indicadas en la nota del inciso b)). ✄<br />

Expliquemos un poco cómo funciona recursión.<br />

Una función o procedimiento que no usa recursión(nosellamaasímisma),<br />

ocupa un lugar en la memoria al momento de correr el programa. Ese<br />

lugar contiene las “cajas” correspondientes a sus variables locales, y también<br />

las instrucciones que debe seguir (recordar la figura 7.2, pág. 75).<br />

Cuando la función o procedimiento se llama a sí misma, podemos pensar<br />

que al ejecutarse el programa se generan automáticamente copias de la función<br />

(tantas como sean necesarias), cada copia con sus propios lugares para código<br />

y variables locales. Cuando la copia de la función o procedimiento que ha sido<br />

llamada por la recursión termina su tarea, el espacio es liberado. Este espacio de<br />

memoria usado por recursión no está reservado por el programa en el momento<br />

de compilar (como sucede con los arreglos y las funciones declaradas), pues no<br />

puede saber de antemano cuántas veces se usará la recursión. Por ejemplo, para<br />

calcular n!, se necesitan unas n copias de la función: cambiando n cambiamos el<br />

número de copias necesarias. Este espacio de memoria especial se llama “stack”<br />

o “pila”, estructura que veremos también en el problema 11.5 yunpocomás<br />

formalmente en la sección 12.1.<br />

✎ Un resultado de computación teórica dice que toda función o procedimiento<br />

recursiva puede reescribirse sin usar recursión con lazos “ while ” y arreglos<br />

(que se usan como “pilas” para ir guardando los datos intermedios).


Pág. 112 Recursión<br />

Problema 11.2. Rehacer el problema 7.5, cambiando la definición de la función<br />

saldo a una recursiva, según la ecuación (7.1). ✄<br />

La recursión también puede usarse en procedimientos:<br />

Problema 11.3. Hacer un programa implementando la clasificación por selección<br />

directa (pág. 101) como un procedimiento recursivo, declarando el procedimiento<br />

como “ seleccion(var a: arreglo; n: integer) ” y haciendo recursión<br />

en n, colocando el mayor elemento en la posición final antes de llamar al<br />

procedimiento con n − 1(vertambién el problema 8.2.e)). ✄<br />

La recursión se puede usar en más de un argumento, i.e. no siempre se “llama<br />

a la función con n − 1” (ver también el problema 11.7):<br />

Problema 11.4. Para m, n ∈ N, consideremos una cuadrícula rectangular de<br />

dimensiones m × n (4 × 3enlafigura11.1), e imaginémosnos que se trata<br />

de un mapa, donde los segmentos son calles y los puntos remarcados son las<br />

intersecciones.<br />

1 4 10 20 35<br />

1<br />

1<br />

3<br />

2<br />

6<br />

3<br />

10<br />

1 1 1 1<br />

Figura 11.1: Contando la cantidad de caminos posibles.<br />

Nos preguntamos de cuántas maneras podremos ir desde la esquina más<br />

hacia el sudoeste, de coordenadas (0, 0), a la esquina más hacia el noreste, de<br />

coordenadas (m, n), si estamos limitados a recorrer las calles únicamente en<br />

sentido oeste–este o sur–norte, según corresponda.<br />

Para resolver el problema, podemos pensar que para llegar a una intersección<br />

hay que hacerlo desde el oeste o desde el sur (salvo cuando la intersección estáen<br />

el borde oeste o sur), y por lo tanto la cantidad de caminos para llegar a la<br />

intersección es la suma de la cantidad de caminos llegando desde el oeste (si<br />

se puede) más la cantidad de caminos llegando desde el sur (si se puede). Los<br />

números en la figura 11.1 indican, para cada intersección, la cantidad de caminos<br />

para llegar allí desde(0, 0) mediante movimientos permitidos.<br />

a) Hacer un programa para calcular la cantidad h(m, n) de caminos para llegar<br />

desde (0, 0) a (m, n), donde m y n son ingresados por el usuario.<br />

b) En cursos de matemática discreta se demuestra que<br />

h(m, n) =<br />

(m + n)!<br />

m! n!<br />

4<br />

(m + n) × (m + n − 1) ×···×(m +1)<br />

= .<br />

n × (n − 1) ×···×1<br />

Incorporar al programa del inciso anterior una función con el cálculo de<br />

h(m, n) (por ejemplo, con un lazo) y comparar con el obtenido anteriormente.<br />

15<br />

5


11.2. Los Grandes Clásicos de la Recursión Pág. 113<br />

c) Modificar el programa del inciso a) demododecalcularlacantidadde<br />

caminos cuando la intersección (r, s) estábloqueada y no se puede pasar<br />

por allí, donde r y s son ingresados por el usuario (0


Pág. 114 Recursión<br />

Tapa del juego original Fotografía del juego<br />

Figura 11.2: Las torres de Hanoi.<br />

type aguja = array[0..10] of integer<br />

y declarar, por ejemplo, var a, b, c: aguja. Usamos la posición 0 para guardar<br />

la cantidad de discos en la aguja (e.g a0 = na), y las siguientes posiciones,<br />

(a1,a2,...,ana), para indicar que en la posición 1 (la de más abajo) de la aguja a<br />

estáeldiscoa1, enla2eldiscoa2, etc. Inicialmente será a =(n, n, n−1,...,2, 1)<br />

(de mayor a menor), b =(0,...)yc =(0,...).<br />

Si hay n discos en la aguja a, y ninguno en la agujas b y c, para poner el<br />

disco más grande en la aguja b, habráque poner los n − 1 restantes en la aguja<br />

c primero, después colocar la n-ésima en la aguja b, y volver a poner los n − 1<br />

en la aguja b. Paramoverlosn−1delaaguja a a la aguja c, habráquemover<br />

n − 2 a la aguja b, pasar el n − 1alaagujac,...<br />

a) Definir un procedimiento pasar (k, x, y, z) que “pase” k discos de la aguja<br />

x alay usando la z (2) , imprimiendo los discos que hay en cada aguja al<br />

terminar.<br />

Sugerencia: parak =1sólo hay que mover la aguja que está enlacimade<br />

x hacia y, yparak>1, podemos poner pasar (k, x, y, z) como consistente<br />

de los siguientes pasos:<br />

pasar(k-1,x,z,y); pasar(1,x,y,z); pasar(k-1,z,y,x)<br />

Sugerencia si la anterior no alcanza:<br />

procedure pasar( k: integer; var x, y, z: aguja);<br />

(* pasar k discos de x a y, usando z *)<br />

begin<br />

if (k > 1) then begin<br />

pasar(k-1, x, z, y);<br />

pasar(1, x, y, z);<br />

pasar(k-1, z, y, x)<br />

end<br />

else begin (* pasar una de x a y *)<br />

y[0] := y[0] + 1; y[y[0]] := x[x[0]]; x[0] := x[0] - 1;<br />

imprimir<br />

end<br />

end;<br />

donde hemos supuesto un procedimiento imprimir que imprime los elemen-<br />

(2) Con movimientos permitidos: recordar que el disco a pasar de una aguja a otra debe ser<br />

menor que cualquier otro disco en esas agujas.


11.2. Los Grandes Clásicos de la Recursión Pág. 115<br />

tos que tiene cada arreglo (hasta la longitud correspondiente). Observar que<br />

k se pasa por valor, pero x, y y z se pasan por referencia: los arreglos pueden<br />

modificarse en el procedimiento.<br />

b) Usar el procedimiento del inciso anterior en un programa que imprima una<br />

sucesión de movimientos para transferir n discos de una aguja a otra. Verificar<br />

el programa para n =1, 2, 3, 4, 5.<br />

c) Agregar un contador para contar la cantidad de veces que se transfiere un<br />

disco de una aguja a otra (en pasar ), e imprimirlo al terminar el programa.<br />

En base a este resultado (para n =1, 2, 3, 4, 5) conjeturar la cantidad<br />

de movimientos necesarios para transferir n discos de una aguja a otra, y<br />

demostrarlo.<br />

Sugerencia: 2 n − 1=1+2+4+···+2 n−1 =1+2(1+2+···+2 n−2 )=<br />

1+2(2 n−1 − 1).<br />

d) Suponiendo que transfieren un disco por segundo, ¿cuánto tardarán los monjes<br />

en transferir los 64 discos? ¿Cuántos años tardaría una computadora en<br />

calcular la solución para n = 64, suponiendo que tarda un nanosegundo por<br />

movimiento (3) (nano = dividir por mil millones)? Bajo la misma suposición<br />

sobre la velocidad de la computadora, ¿cuál es el valor máximo de n para<br />

calcular los movimientos en 1 minuto?<br />

✎ La suposición que lo que tarda la computadora por movimiento es<br />

independiente de n es una aproximación muy buena. Si es posible,<br />

comprobarla (tomando por ejemplo n =19, 20,...), sin imprimir los<br />

movimientos, y estimar lo que tarda la computadora por movimiento<br />

en cada caso (del orden de micro segundo, micro = dividir por un<br />

millón).<br />

✎ En el problema 11.9 se da una variante para resolver el problema de las<br />

torres de Hanoi sin usar arreglos.<br />

✎ La estructura que estamos usando para los arreglos, agregando atrás y<br />

sacando también desde atrás, es un ejemplo de pila (como la de platos),<br />

sobre la que hablaremos más en la sección 12.1.<br />

Hay muchas variantes del problema de las torres de Hanoi, por ejemplo:<br />

¿qué pasa si los discos no están inicialmente todos sobre una misma aguja<br />

(pero respetan la distribución de mayor a menor)?, ¿qué pasa si hay más de<br />

tres agujas? ✄<br />

Problema 11.6 (Números de Fibonacci). En 1202 Fibonacci propuso el<br />

siguiente problema en su libro Liber Abaci:<br />

¿Cuántos pares de conejos se producen a partir de una única pareja<br />

en un año si cada mes cada par de conejos da nacimiento a un nuevo<br />

par, el que después del segundo mes se reproduce, y no hay muertes?<br />

a) Resolver el problema.<br />

Los números que aparecen en la solución de este problema se conocen como<br />

números de Fibonacci, definidos recursivamente como:<br />

f1 =1, f2 =1, y fn = fn−1 + fn−2 para n ≥ 3.<br />

(3) ¡Y que no hay cortes de luz!


Pág. 116 Recursión<br />

b) Hacer un programa para calcular los números de Fibonacci:<br />

i) Usando recursión sobre dos parámetros con dos condiciones iniciales.<br />

ii) Sin usar recursión. Sugerencia: un esquema posible como función es<br />

a := 1; b := 1;<br />

for i := 3 to n do begin c := a + b; b := a; a := c end;<br />

fibonacci := a<br />

c) La fórmula de Euler-Binet establece que<br />

2n √ . (11.4)<br />

5<br />

Créase o no, el miembro derecho es un número entero (¿podrías decir por<br />

qué, si no supieras el resultado?).<br />

Implementar el miembro derecho de esta igualdad como función y comparar<br />

con los resultados del inciso anterior.<br />

d) Según la fórmula 11.4, fn crece exponencialmente, porloquerápidamente<br />

toma valores muy grandes, y al implementarse como función debe retornar<br />

valores de tipo “ real ” (como en el caso de n!). Encontrar el máximo n tal<br />

que fn ≤ maxint.<br />

fn = (1 + √ 5) n − (1 − √ 5) n<br />

Leonardo Bigollo es el verdadero nombre de Leonardo de Pisa (1180–1250),<br />

también conocido como Fibonacci (contracción de las palabras “hijo de Bonacci”).<br />

Además de “Liber Abaci”, publicó numerosos tratados sobre teoría de<br />

números, geometría y la relación entre ellos. Sin embargo, fue prácticamente<br />

ignorado durante la Edad Media, apareciendo sus resultados nuevamente publicados<br />

(por Maurico) unos 300 años después.<br />

Jacques Binet (1786–1856) publicó lafórmula para los números de Fibonacci<br />

en 1843, pero ya había sido publicada por Leonhard Euler (1707–1783)<br />

en 1765. Sin embargo, aún antes A. de Moivre (1667–1754) la había publicado<br />

(y en forma más general) en 1730.<br />

Mucho después de Fibonacci, se observó quelosnúmeros fn aparecen en<br />

muy diversos contextos, algunos insospechados como en la forma de las flores<br />

del girasol, y son de importancia tanto en las aplicaciones prácticas como<br />

teóricas. Por ejemplo, han sido usados para resolver problemas de confiabilidad<br />

de comunicaciones.<br />

En cuanto a aplicaciones teóricas, vale la pena mencionar que en el Congreso<br />

Internacional de Matemáticas de 1900, David Hilbert propuso una serie<br />

de problemas que consideró de importancia para resolver durante el siglo XX.<br />

Entre ellos, el décimo pide encontrar un algoritmo para determinar si un polinomio<br />

con coeficientes enteros, arbitrariamente prescripto, tiene raíces enteras<br />

(resolver la ecuación diofántica asociada). Recién en 1970 pudo obtenerse una<br />

respuesta al problema, cuando el matemático ruso Yuri Matijasevich (quien<br />

tenía 22 años) demostró que el problema es irresoluble, es decir, no existe algoritmo<br />

que pueda determinar si una ecuación diofántica polinomial arbitraria<br />

tiene soluciones enteras. En su demostración, Matijasevich usa la tasa de crecimiento<br />

de los números de Fibonacci. ✄<br />

11.3. Problemas Adicionales<br />

Problema 11.7. Usando recursión, reescribir el algoritmo de Euclides (sección<br />

5.2.2) para encontrar el máximo común divisor entre dos enteros positivos.<br />

Sugerencia: mcd(a, b) =mcd(a − b, b) sia>b. ✄


11.3. Problemas Adicionales Pág. 117<br />

Problema 11.8. En este problema queremos encontrar los factores primos de<br />

n ∈ N usando recursión.<br />

a) Podemos probar si 2 divide a n y en caso afirmativo probar nuevamente si<br />

2 divide a n/2, o bien probar con 3, y así sucesivamente, imprimiendo cada<br />

factor encontrado:<br />

procedure factorizar(n, k: integer);<br />

begin<br />

if (n > 1) then<br />

if ((n mod k) = 0) then begin<br />

write(k:10);<br />

factorizar(n div k, k)<br />

end<br />

else factorizar(n, k+1)<br />

end;<br />

haciendo la llamada “ factorizar(n,2)” enlaparteprincipal.<br />

✎ Refiriéndonos a comentarios anteriores, observar que en el procedimiento<br />

n y k son llamados por valor y no referencia.<br />

i) Ver que el procedimiento es correcto y que, en particular, los números<br />

que se imprimen son siempre primos.<br />

ii) Hacer un programa para escribir los factores de n ∈ N, n>1, usando<br />

este procedimiento.<br />

b) Recordando los problemas 5.19 y 5.20, ver que se puede mejorar el procedimiento<br />

del inciso anterior a:<br />

procedure factorizar(n, inic: integer);<br />

var x: real;<br />

begin<br />

x := sqrt(n);<br />

while ((inic x) then writeln(n:10) (* n es primo *)<br />

else begin<br />

write(inic:10);<br />

factorizar(n div inic, inic)<br />

end<br />

end; ✄<br />

Problema 11.9 (Torres de Hanoi sin arreglos). Flor, que siempre le lleva<br />

la contra al profe y está compitiendo con él, hizo un programa para resolver el<br />

problema de las torres de Hanoi sin usar arreglos. Más aún, ¡cambió arreglos<br />

por caracteres!:<br />

program hanoisinarreglos(input, output);<br />

(*<br />

Solucion recursiva de las torres de Hanoi,<br />

sin usar arreglos.<br />

*)<br />

type aguja = char;


Pág. 118 Recursión<br />

var n: integer;<br />

procedure pasar( n: integer; x, y, z: aguja);<br />

(* pasar n discos de x a y usando z *)<br />

begin<br />

if (n > 1) then begin<br />

pasar(n-1, x, z, y);<br />

write(’pasar el disco ’, n:1);<br />

writeln(’ de "’, x, ’" a "’, y,’"’);<br />

pasar(n-1, z, y, x)<br />

end<br />

else begin<br />

write(’pasar el disco 1’);<br />

writeln(’ de "’, x, ’" a "’, y,’"’)<br />

end<br />

end;<br />

begin<br />

writeln(’** Problema de las torres de Hanoi:’);<br />

writeln(’ Pasar n discos de la aguja "a" a la "b"’);<br />

write(’ usando la "c", mediante movimientos ’);<br />

writeln(’ permitidos.’);<br />

writeln;<br />

write(’ Entrar el numero de discos: ’); readln(n);<br />

writeln;<br />

pasar(n, ’a’, ’b’, ’c’);<br />

writeln; writeln(’** Fin **’)<br />

end.<br />

¿Es correcto el programa de Flor? ¿Por qué? ✄<br />

Problema 11.10. Recordando la fórmula de Euler-Binet (11.4), comparar el<br />

número de Fibonacci fn con el redondeo (“ round ”) de<br />

(1 + √ 5) n<br />

2n √ .<br />

5<br />

¿A partir de qué n son iguales?, ¿podrías decir por qué? ✄<br />

11.4. Comentarios Bibliográficos<br />

y<br />

Las fotografías en la figura 11.2 están tomadas de<br />

http://www.cs.wm.edu/~pkstoc/<br />

http://en.wikipedia.org/wiki/Tower of Hanoi<br />

respectivamente.


Capítulo 12<br />

Generando objetos<br />

combinatorios<br />

Muchos de los problemas que vimos en el capítulo anterior se pueden reescribir<br />

sin recursión con lazos “ for ”, como el factorial, selección directa, los<br />

números de Fibonacci, o aún la cantidad de caminos del problema 11.4, ynos<br />

quedamos con la impresión de que recursión no es muy útil. Otro problema muy<br />

distinto al de contar objetos es generarlos, por ejemplo para encontrar alguno o<br />

todos los que satisfacen cierto criterio, y aquí es donde recursión muestra toda<br />

su potencia.<br />

Puesto que el número de objetos a generar puede ser muy grande, como<br />

2 n o n!, es conveniente no tener —por ejemplo— un arreglo para cada uno de<br />

ellos, sino tener uno solo que se va modificando a medida que vamos creando<br />

los objetos. Una estructura particularmente útil para esta modificación es la de<br />

“pila”, con la que comenzamos.<br />

12.1. Pilas y colas<br />

Las pilas, como las de platos, y las colas, como las de supermercados, son dos<br />

estructuras familiares que trataremos de formalizar un poco aquí, más que nada<br />

para poner en perspectiva lo que estamos haciendo y no siendo estrictamente<br />

rigurosos.<br />

Tanto en pilas como en colas podemos pensar que tenemos una serie de<br />

objetos —todos del mismo tipo— esperando en línea a ser tratados. En el caso<br />

de la pila, o cola lifo, por “last in first out”, el último en llegar es el primero en<br />

ser tratado. En cambio en lo que denominamos comúnmente cola (sin aditivos),<br />

omás formalmente cola fifo, por “first in first out”, el primero en llegar es el<br />

primero en ser tratado.<br />

No es difícil imaginar distintas acciones comunes:<br />

Crear o inicializar la cola, construyendo la cola “vacía”.<br />

Agregar un elemento, ubicándolo al final y actualizando la cola.<br />

Quitar un elemento, tomando, según corresponda, el primero o el último<br />

y actualizando la cola.


Pág. 120 Generando objetos combinatorios<br />

Destruir la cola, cuando no la necesitamos más, limpiando lo que ensuciamos.<br />

En fin, podríamos agregar otras, como averiguar el estado de la cola (si<br />

está vacía o llena).<br />

Es tradicional en computación llamar con distintos nombres a estas acciones,<br />

según se trate de una pila o cola. Por ejemplo, agregar el elemento a en una pila<br />

se llama push yenlacolaput. En cambio, el quitar un elemento se llama pop<br />

para pilas y get para colas.<br />

No es casualidad que put y get sean nombres en Pascal de funciones o procedimientos<br />

para entrada o salida (que no cubriremos, siendo más “primitivas”<br />

que read y write). Efectivamente, la entrada y salida en la computadora se<br />

implementan como colas fifo: los datos se leen o escriben en el orden en que<br />

van surgiendo. Dado que put y get tienen significados especiales en Pascal, es<br />

conveniente evitar estos nombres.<br />

El concepto de cola, que tiene muchísimas generalizaciones, constituye un<br />

tipo de datos abstracto (TDA): no importa tanto cómo se va a implementar o<br />

representar en la máquina, sino saber que contamos con las operaciones de crear<br />

o destruir la cola, agregarle o quitarle un elemento, etc.<br />

Sin embargo, la implementación de una cola como “fifo” o “lifo” puede tener<br />

consecuencias decisivas en los algoritmos, como veremos en el capítulo 13.<br />

Además, en lenguajes no tan abstractos como Pascal, debemos tener cuidado en<br />

la implementación de TDA’s. En el capítulo 14 introduciremos punteros y listas<br />

encadenadas de Pascal, que son más apropiados para implementar colas, pero a<br />

fin de no complicar la presentación, por ahora “nos arreglaremos con arreglos”.<br />

Como sabemos, si queremos trabajar con un arreglo tenemos que declararlo<br />

al comienzo, dando su dimensión y el tipo de elementos. Por lo tanto, si vamos a<br />

representar una cola con un arreglo, no podemos “crearlo de la nada”, y tenemos<br />

que prever su existencia y dar una dimensión adecuada al problema. Del mismo<br />

modo, no podemos “destruirlo”, y nos limitaremos a ignorarlo cuando no lo<br />

necesitemos más.<br />

Suponiendo que hemos declarado el tipo “ dato ”, que puede ser un registro<br />

u otro arreglo, podemos implementar una pila declarando<br />

pila: array[1..MAXP] of dato<br />

donde MAXP es una constante apropiada. Si npila es la cantidad de elementos,<br />

declarado con la misma localidad que pila, podemos hacer:<br />

Crear la pila:<br />

procedure inicializar;<br />

begin npila := 0 end;<br />

Agregar d:<br />

procedure push(d: dato);<br />

begin<br />

if (npila < MAXP) then begin<br />

npila := npila + 1; pila[npila] := d<br />

end<br />

end;


12.2. Generando Subconjuntos Pág. 121<br />

✎ Siguiendo con la filosofía de no poner en general mensajes de<br />

aviso, tratando de ver el bosque y no el árbol, no hemos agregado<br />

acciones para el caso en que npila = MAXP antes de<br />

incorporar el dato.<br />

Quitar un elemento:<br />

function pop: dato;<br />

begin<br />

if (npila > 0) then begin<br />

pop := pila[npila]; npila := npila - 1<br />

end<br />

end;<br />

✎ Valen los mismos comentarios anteriores: no hemos previsto acciones<br />

para cuando se quiera sacar un elemento de una cola<br />

vacía. En general, preguntaremos si hay elementos antes de sacar.<br />

Observar la simetría entre las acciones de push y pop: una realiza exactamente<br />

los pasos inversos de la otra. Esta simetría se traslada a que push es un<br />

procedimiento con el dato como parámetro, mientras que pop es una función<br />

que devuelve el dato (y por lo tanto debe hacerse alguna asignación o similar).<br />

Para colas (fifo), las cosas son ligeramente diferentes. En vez de tener un índice<br />

como en el caso de la pila, mantenemos dos: uno, digamos ppocola,señalando<br />

el principio de la cola en el arreglo, y otro, digamos fincola, señalando el final.<br />

fincola se incrementa al agregar un dato, mientras que ppocola aumenta cuando<br />

se extrae un dato, de modo que los elementos “vivos” en principio estarán entre<br />

ppcola y fincola (inclusivo en ambos casos).<br />

Claro que si la cola está definida como un arreglo de MAXC elementos,<br />

las cosas se complican cuando agregamos más de MAXC elementos a la cola<br />

(aún cuando hayamos quitado algunos), y necesitamos usar aritmética módulo<br />

MAXC . No entramos en detalles porque en los ejemplos que daremos en<br />

los capítulos posteriores supondremos que MAXC es lo suficientemente grande<br />

como para evitar este problema.<br />

Volviendo a las pilas, no podemos dejar de observar que los procedimientos<br />

inicializar y push son viejos conocidos que usamos al leer los datos de un arreglo,<br />

por ejemplo en el programa maximocaracter (pág. 192). También hemos visto<br />

un atisbo del uso de pilas en el problema 11.5 de las torres de Hanoi, donde las<br />

agujas eran pilas en sí.<br />

En la próxima sección veremos que la técnica de mezclar recursión con pilas<br />

puede usarse para generar distintos objetos combinatorios, aunque las pilas no<br />

siempre aparecerán explícitamente.<br />

12.2. Generando Subconjuntos<br />

Problema 12.1. Supongamos que queremos imprimir todas las cadenas de bits<br />

(arreglos de 0’s y 1’s) de longitud n. Dado que las cadenas de bits de longitud<br />

n se obtienen agregando un 0 o un 1 a las cadenas de longitud n − 1, podríamos<br />

declarar un arreglo global a como<br />

a: array[1..10] of integer,


Pág. 122 Generando objetos combinatorios<br />

donde iremos construyendo las cadenas, y hacer la llamada en el cuerpo principal<br />

a“cadena(1)”, donde cadena está dadapor:<br />

procedure cadena(k: integer);<br />

var i, j: integer;<br />

begin<br />

for i := 0 to 1 do begin<br />

a[k] := i; (* poner 0 o 1 en el lugar k *)<br />

if (k < n) then cadena(k+1)<br />

else begin<br />

(* imprimir la cadena cuando k = n *)<br />

write(’ ’);<br />

for j := 1 to n do write(a[j]:1);<br />

writeln<br />

end (* else *)<br />

end (* for *)<br />

end;<br />

Observar que usamos al arreglo a como una pila. Para k fijo, el elemento<br />

en la posición k tiene un valor de 0 cuando i = 0, que se mantiene para valores<br />

superiores a k pero luego es cambiado para i = 1, y obviamente cambia<br />

varias veces cuando es llamado desde valores inferiores a k. Sin embargo, k es<br />

el parámetro del procedimiento y no una variable global.<br />

a) Hacer una prueba de escritorio del procedimiento cuando n =3.<br />

b) Hacer un programa que dado n ∈ N imprima todas las cadenas de bits de<br />

longitud n, siguiendo las indicaciones anteriores.<br />

c) Agregar un contador (global) para contar las cadenas de longitud n, yver<br />

que la cantidad de cadenas es 2n .<br />

d) En el procedimiento cadena propuesto, se va “hacia adelante”, llamando<br />

a“cadena(1) ” en el cuerpo principal y se va aumentando el valor del<br />

parámetro k en cada llamada del procedimiento hasta llegar a k = n, ya<br />

que n es global. Esto contrasta con el uso de recursión de, por ejemplo, el<br />

factorial, donde vamos “hacia atrás” disminuyendo el valor del parámetro en<br />

cada llamada. ¿Cómo podría redefinirse el procedimiento de modo de hacer<br />

la llamada “ cadena(n)” en el cuerpo principal, y que en vez comparar k<br />

con n se compare con 0 o 1?<br />

✎ En el problema 12.7 vemos otra forma de encontrar las cadenas de bits, sin<br />

usar recursión. ✄<br />

Problema 12.2 (Subconjuntos). En el problema anterior esencialmente se<br />

construyen todos los subconjuntos de {1, 2,...,n}, ya que las cadenas de bits de<br />

longitud n se pueden considerar como vectores característicos. Dado un conjunto<br />

A ⊂ {1, 2,...,n} definimos su vector característico, b(A) = (b1,b2,...,bn),<br />

mediante<br />

<br />

1 si i ∈ A,<br />

bi =<br />

0 si no.<br />

Es claro que dos conjuntos distintos tienen vectores característicos distintos,<br />

y que por otro lado, dada una cadena de bits podemos encontrar un conjunto<br />

A tal que b(A) sea esa cadena. Por lo tanto, hay una correspondencia biunívoca<br />

entre cadenas de bits de longitud n y subconjuntos de {1, 2,...,n}.


12.2. Generando Subconjuntos Pág. 123<br />

Modificar el programa del problema 12.1 para que en vez de imprimir cadenas<br />

de bits, imprima el subconjunto correspondiente en {1, 2,...,n}, representando<br />

al conjunto vacío con una raya “ - ”.<br />

Por ejemplo, si n = 2 la salida debería ser algo como:<br />

- 1 2 1 2 ✄<br />

Una vez que sabemos cómo generar una familia de objetos, es más sencillo<br />

generar o contar objetos de la familia con características particulares. Por<br />

ejemplo:<br />

Problema 12.3. Supongamos que queremos contar la cantidad h(n) de cadenas<br />

de n bits que no contienen dos 0’s sucesivos:<br />

a) Hacer un programa para calcular h(n) paran∈N, usando el programa<br />

del problema 12.1 y una variante de búsqueda lineal para encontrar dos 0’s<br />

consecutivos en cada cadena construida, verificando si se trata de una cadena<br />

válida antes de aumentar el contador. Sugerencia: recordar el problema 9.3.<br />

b) Comparar el número h(n) obtenido anteriormente con los números de Fibonacci<br />

y hacer un nuevo programa para calcular h(n) directamente. ✄<br />

Problema 12.4. En este problema imprimiremos todos los caminos que hemos<br />

contado en el problema 11.4. Para ello consideramos una arreglo global camino<br />

en el que guardaremos las “intersecciones” o “esquinas” por las que hay que<br />

pasar. Como éstas tienen dos coordenadas, digamos x y y, declaramos<br />

type esquina = record x, y: integer end;<br />

y<br />

camino: array[1..MAXK] of esquina;<br />

donde MAXK es una constante para la máxima longitud del camino. En el problema<br />

original todos los caminos tienen longitud m+n,peropodrían ser distintas<br />

si, por ejemplo, también consideráramos la posibilidad de ir en diagonales de<br />

suroeste a noreste. Para nuestros ejemplos, 10 o 20 son valores razonables para<br />

MAXK .<br />

También consideramos a los datos m y n como variables globales, y agregamos<br />

la variable global k que indicará la longitud del camino construido. Inicialmente<br />

tendremos k = 0, puesto que no tenemos camino.<br />

El trabajo lo hará el procedimiento llegardesde, haciendo la única llamada<br />

“ llegardesde(0,0)” enelcuerpoprincipal:<br />

procedure llegardesde(i, j: integer);<br />

var r: integer;<br />

begin<br />

k := k + 1;<br />

with camino[k] do begin x := i; y := j end;<br />

if (i < m) then llegardesde(i+1,j);<br />

if (j < n) then llegardesde(i,j+1);<br />

if ((i = m) and (j = n)) then (* llegamos *)<br />

begin<br />

(* imprimir *)<br />

write(’ ’);<br />

for r := 1 to k do


Pág. 124 Generando objetos combinatorios<br />

with camino[r] do<br />

write(’ (’, x:1, ’,’, y:1, ’)’);<br />

writeln<br />

end; (* if i = m and j = n *)<br />

k := k - 1<br />

end;<br />

Estudiemos este procedimiento, haciendo una “prueba de escritorio” en todo<br />

caso:<br />

• k, inicialmente en 0, se incrementa en 1 al entrar al procedimiento, y se<br />

incorpora la intersección (i, j) alcamino en la (nueva) posición k.<br />

Es decir, k indica la cantidad de objetos en la pila camino. Al comienzo,<br />

la pila está vacía, y se van incorporando elementos atrás.<br />

A diferencia del problema 12.1, k es global y no el parámetro del procedimiento.<br />

• Si i


12.3. Generando permutaciones Pág. 125<br />

y por lo tanto no lo haremos aquí, que si el conjunto tiene n elementos entonces<br />

hay n! permutaciones posibles.<br />

Problema 12.5 (Generación de permutaciones). En este problema, copiando<br />

las ideas de los problemas anteriores, generamos todas las permutaciones<br />

de {1, 2,...,n} usando el procedimiento poner :<br />

procedure poner(k: integer);<br />

(* poner en la posicion k los que faltan *)<br />

var j: integer;<br />

begin<br />

if (k < n) then<br />

(* buscar y agregar los que faltan *)<br />

for j := 1 to n do begin<br />

if (falta[j]) then begin<br />

falta[j] := false; a[k] := j;<br />

poner(k+1); (* ir a proxima posicion *)<br />

falta[j] := true (* borrar huellas *)<br />

end (* if *)<br />

end (* for *)<br />

else (* k = n: nueva permutacion *)<br />

begin<br />

(* buscar y agregar el que falta *)<br />

j := 0; repeat j := j + 1 until falta[j];<br />

a[n] := j;<br />

(* imprimir *)<br />

write(’ ’); for j := 1 to n do write(a[j]:3);<br />

writeln<br />

end<br />

end;<br />

donde a es un arreglo global en el que guardamos la permutación que estamos<br />

construyendo, y falta es un arreglo cuyos elementos son de tipo “ boolean ”(y<br />

tiene la misma longitud de a), de modo que falta i es falso o verdadero de acuerdo<br />

asielelementoi ya ha sido colocado o no (respectivamente) en la permutación.<br />

Observamos que<br />

• Los arreglos a y falta deben tener una longitud adecuada. 10 es suficiente<br />

para nosotros.<br />

• n debe ser una variable global. En cambio, k es el parámetro del procedimiento,comoenelproblema12.1.<br />

• Inicialmente “faltan” todos los elementos en la permutación, i.e. falta i es<br />

verdadero para todo i =1,...,n.<br />

• En cualquier paso, cuando se agrega el elemento j en la posición k (k


Pág. 126 Generando objetos combinatorios<br />

los valores en lugares arbitrarios y en cada momento son importantes todos<br />

los valores, no sólo los primeros.<br />

• Se hace la única llamada “ poner(1) ”enelcuerpoprincipal.<br />

a) Usando las ideas anteriores, hacer un programa para generar todas las permutaciones<br />

de n elementos.<br />

b) En el procedimiento poner , al buscar los elementos que faltan se pone<br />

if (k < n) then<br />

for j := 1 to n do begin<br />

if (falta[j]) then begin<br />

.<br />

end (* if *)<br />

end (* for *)<br />

¿Qué función cumple el par “ begin-end” que encierra a “ if ” (el primer<br />

“ begin ”conelúltimo “ end ”) en este caso?, ¿es redundante?<br />

¿Sería equivalente poner<br />

if (k < n) then begin<br />

for j := 1 to n do<br />

if (falta[j]) then begin<br />

.<br />

end (* if *)<br />

end (* if *) ?<br />

c) Eliminar (comentándolo) el renglón “ falta[j] := true ” del procedimiento<br />

poner , y comprobar el comportamiento.<br />

d) Agregar un contador, como en los problemas anteriores, a fin de ir contando<br />

las permutaciones obtenidas, imprimiendo cada permutaciónconsunúmero<br />

de orden.<br />

✎ Hay varios algoritmos para generar las permutaciones de {1,...,n}, algunos<br />

bastante diferentes al que presentamos, y otros más eficientes. Observar<br />

que con el presentado, las permutaciones obtenidas están ordenadas “lexicográficamente”,<br />

cosa que no ocurre en otros algoritmos. ✄<br />

12.4. Árboles binarios ordenados<br />

Como mencionáramos al presentar los métodos de clasificación en la sección<br />

10.3, una de las mayores causas de ineficiencia al clasificar es el “movimiento”<br />

de datos, problema que se agudiza al mover “grandes” datos como<br />

arreglos o registros. En esta sección nos preocuparemos por dar un método donde<br />

clasificamos datos pero una vez construido un registro éste “no se mueve”.<br />

Hasta ahora nos hemos concentrado en la estructura “lineal” de los arreglos<br />

para guardar información, como se ilustra en la figura 12.1, donde cada punto<br />

o nodo representa un elemento del arreglo, y las flechas señalan al siguiente<br />

elemento.<br />

Figura 12.1: Una estructura “lineal”.


12.4. Árboles binarios ordenados Pág. 127<br />

Pero podemos pensar en una configuración como la de la figura 12.2, donde<br />

también tenemos nodos unidos por flechas. Los nodos están en distintos “pisos”<br />

o niveles, marcados por las rectas punteadas (sí, a medida que bajamos el nivel<br />

aumenta).<br />

nivel 0<br />

nivel 1<br />

nivel 2<br />

nivel 3<br />

Figura 12.2: Un árbol binario ordenado.<br />

Si tuviéramos nodos hasta un nivel k, vemos que para ir desde el punto<br />

superior a cualquier otro usando los segmentos, necesitamos a lo sumo k pasos.<br />

Por lo tanto, podemos ir desde cualquier punto a otro cualquiera en a lo sumo<br />

2k pasos, aún cuando puede haber hasta<br />

1+2+4+···+2 k =2 k+1 − 1<br />

nodos. Esta observación es muy importante si guardamos y buscamos datos en<br />

los nodos: la diferencia entre las estructuras de las figuras 12.2 y 12.1 es similar<br />

a la que hay entre búsqueda binaria y búsqueda lineal.<br />

Unaestructuracomoladelafigura12.2 se llama árbol binario ordenado<br />

o simplemente árbol binario (1) .Elnododemás arriba (en el nivel 0) se llama<br />

raíz, y cada nodo está conectadohaciaabajocon0,1o2(ynomás) hijos, ala<br />

izquierdaoaladerecha.<br />

✎ El nombre de árbol binario ordenado parece un poco largo. Informalmente,<br />

se llama árbol por la forma de “ramas”, aunque la raíz se representa arriba,<br />

binario porque cada rama se divide en a lo sumo 2, y ordenado porque<br />

distinguimos entre el hijo a la izquierda y a la derecha.<br />

✎ Como vimos, un árbol binario con k niveles puede tener hasta 2 k+1 − 1<br />

nodos. En contrapartida, si tiene exactamente k niveles, no puede tener<br />

menos de k + 1 nodos.<br />

Veamos cómo usar esta estructura para guardar información.<br />

Supongamos que, como en el problema 10.15, ingresamos letras, por ejemplo<br />

‘ b ’, ‘ d ’, ‘ c ’, ‘ d ’, ‘ a ’, ‘ d ’, ‘ a ’, ‘ a ’, ‘ d ’, ‘ e ’, ‘ d ’, ‘ e ’, ‘ a ’, ‘ b ’, ‘ a ’,<br />

en ese orden, y queremos imprimir la cantidad de veces que apareció cada una<br />

de ellas, pero ordenadas alfabéticamente, i.e. queremos una salida como<br />

Caracter Cantidad de apariciones<br />

a 5<br />

b 2<br />

c 1<br />

d 5<br />

e 2<br />

(1) Sólo veremos árboles binarios ordenados.


Pág. 128 Generando objetos combinatorios<br />

Si declaramos, en forma similar al problema 10.15,<br />

type<br />

tipodato = char; (* el tipo de datos a ingresar *)<br />

nodo = record<br />

llave: tipodato; (* dato *)<br />

cuenta: integer (* veces que aparecio *)<br />

end;<br />

tipoarreglo = array[1..MAXN] of nodo;<br />

y guardamos los datos en un arreglo de tipo “ tipoarreglo”, digamos arreglo,<br />

después de ingresar los datos como en el aquel problema, y antes de clasificarlos,<br />

tendríamos una disposición como en la figura 12.3.<br />

‘ b ’ 2 ‘ d ’ 5 ‘ c ’ 1 ‘ a ’ 5 ‘ e ’ 2<br />

arreglo 1 arreglo 2 arreglo 3 arreglo 4 arreglo 5<br />

Figura 12.3: Disposición del arreglo de registros luego de ingresar los datos.<br />

Para dar una estructura de árbol binario a los datos, hacemos lo siguiente: el<br />

primer dato ingresado es la raíz del árbol, y al ingresar cada dato subsiguiente lo<br />

comparamos con los ya existentes, yendo hacia abajo a la izquierda si el nuevo<br />

dato es menor o hacia la derecha si es mayor (o incrementando cuenta si es<br />

igual). Así, al terminar de ingresar los datos en nuestro ejemplo quedaría un<br />

árbol binario como el de la figura 12.4.<br />

‘ b ’ 2<br />

‘ a ’ 5 ‘ d ’ 5<br />

‘ c ’ 1 ‘ e ’ 2<br />

Figura 12.4: Disposición del árbol binario luego de ingresar los datos.<br />

Después de mirar un rato esta figura, nos damos cuenta que si “proyectamos”<br />

los registros sobre una recta horizontal como en la figura 12.5, obtenemos los<br />

registros clasificados alfabéticamente. Después de pensar otro rato más, nos<br />

damos cuenta que no se trata de una casualidad: en la figura 12.4 todo lo<br />

que queda a la izquierda y abajo de un registro tiene que tener menor llave<br />

yanálogamente hacia la derecha y abajo. Así, en nuestro ejemplo, todo lo que<br />

es menor que ‘ b ’, i.e. ‘ a ’, está a la izquierda, mientras que ‘ c ’, ‘ d ’y‘e ’están<br />

a la derecha. Cuando miramos a ‘ d ’, ‘ c ’está a la izquierda, pero también ‘ b ’<br />

(que tenía a ‘ d ’asuderecha)y‘a ’ (que estaba a la izquierda de ‘ b ’), etc.<br />

‘ a ’ 5 ‘ b ’ 2 ‘ c ’ 1 ‘ d ’ 5 ‘ e ’ 2<br />

Figura 12.5: Los registros de la figura 12.4 “proyectados”.<br />

Nos queda la inquietud de cómo guardar las “flechas” en la computadora,<br />

i.e. la información sobre los hijos a izquierda y derecha. Para esto agregamos a<br />

los registros dos campos donde se guardará elíndice del hijo correspondiente:


12.4. Árboles binarios ordenados Pág. 129<br />

type<br />

indice = integer; (* para senialar los indices *)<br />

tipodato = char; (* el tipo de datos a ingresar *)<br />

nodo = record<br />

llave: tipodato; (* dato *)<br />

cuenta: integer; (* veces que aparecio *)<br />

izquierda: indice; (* hijo a la izquierda *)<br />

derecha: indice (* hijo a la derecha *)<br />

end;<br />

tipoarbol = array[1..MAXN] of nodo;<br />

Aún cuando el arreglo con los datos ingresados sigue formando una pila,<br />

ahora nos queda una disposición como en la figura 12.6, donde con 0 indicamos<br />

que no hay hijos (a izquierda o derecha) y, claro, dibujamos las flechas sólo<br />

para orientarnos (no están en la computadora). Observar que esta figura es<br />

básicamente la figura 12.3 a la que hemos agregado flechas, pero también es<br />

equivalente a la figura 12.4.<br />

‘ b ’ 2 4 2 ‘ d ’ 5 3 5 ‘ c ’ 1 0 0 ‘ a ’ 5 0 0 ‘ e ’ 2 0 0<br />

Figura 12.6: Los registros con índices para la estructura de árbol binario.<br />

Ya estamos en condiciones de abordar el siguiente problema:<br />

Problema 12.6. El programa arbolbinario, muestra la construcción de un árbol<br />

binario ordenado usando arreglos como indicamos anteriormente.<br />

En los nodos se guarda información, que en nuestro ejemplo es llave y cuenta.<br />

En llave guardamos un entero, pero podría ser una palabra de un texto, o<br />

un apellido, etc., mientras que en cuenta guardamos el número de veces que<br />

apareció esedato.Enelárbol, un nodo tiene a su izquierda un subárbol cuyos<br />

nodos tienen menor llave, y a su derecha nodos con mayor llave.<br />

A la salida se imprimen los datos ordenados por orden creciente de llave y<br />

las veces que apareció el dato.<br />

Estudiando el programa, observamos que<br />

• Cada nodo guardará cuatro datos: la llave, la cantidad de veces que apareció,<br />

y cómo encontrar a sus hijos. Por lo tanto necesitamos un registro<br />

con 4 campos.<br />

• Los nodos con la información se guardan en un arreglo de nodos llamado<br />

(¡curiosamente!) arbol.<br />

• Hay una función booleana entrardatos, modificando un poco lo hecho en<br />

el procedimiento leerarreglo del problema 8.1.<br />

• El árbol se va construyendo en el arreglo arbol como pila con narbol elementos:<br />

a medida que llega nueva información se aumenta narbol yse<br />

incorpora al final.


Pág. 130 Generando objetos combinatorios<br />

• Pero la información de la estructura de árbol —bien diferente a la de<br />

pila— se guarda en los registros: en izquierda (o derecha) colocamos la<br />

posición en el arreglo del hijo a la izquierda (o derecha). Para indicar que<br />

no hay un hijo (a izquierda o derecha) introducimos la constante nada,que<br />

podría tomar cualquier valor “imposible” para índices del arreglo arbol y<br />

que hemos puesto en 0.<br />

• Para procesar los datos ingresados, usamos el procedimiento recursivo<br />

binario que primero se fija si se está en una posición “vacía”, i.e. sin<br />

datos, en cuyo caso se agrega un nodo al árbol, se guarda el dato, se<br />

coloca cuenta en 1, y se inicializan los hijos a nada pues están vacíos.<br />

Si la posición no está vacía, se pregunta si el dato es el mismo que se<br />

guarda en ese nodo, en cuyo caso se incrementa cuenta, osidebeirala<br />

izquierdaencasoqueseamenoroaladerecha.<br />

• El procedimiento enorden imprime los datos en (¡ejem!) orden con la cantidad<br />

de apariciones en forma recursiva: para mantener el orden, deben<br />

imprimirse primero los descendientes a la izquierda, luego el nodo mismo,<br />

y finalmente los descendientes a derecha. La condición “de parada” para<br />

la recursión es encontrar el valor nada.<br />

Observar que no necesitamos intercambiar los registros para poder imprimirlos<br />

ordenadamente (recordar la “proyección” mencionada anteriormente).<br />

a) Cambiar la impresión en orden por post orden (primero se imprimen los<br />

subárboles y después el nodo) y por pre orden (primero el nodo y después<br />

los subárboles).<br />

b) Cambiar el procedimiento enorden para imprimir los datos clasificados de<br />

mayor a menor (en vez de menor a mayor).<br />

c) Cambiar el procedimiento enorden para imprimir también el nivel en que<br />

se ha alojado la información.<br />

Sugerencia: agregar un argumento para el nivel, formando parte de la recursión,<br />

o, alternativamente, redefinir los nodos de modo de incluir el nivel<br />

cuando se construyen.<br />

Sugerencia si la anterior no alcanza: paralaprimervariantepodríamos<br />

poner<br />

procedure enorden(w: indice; nivel: integer);<br />

begin<br />

if (w nada) then<br />

with arbol[w] do begin<br />

enorden(izquierda, nivel+1);<br />

writeln(llave:10, cuenta:20, nivel:20);<br />

enorden(derecha, nivel+1)<br />

end<br />

end;<br />

haciendo la llamada “ enorden(raiz, 0) ” en la parte principal.<br />

d) Agregar al programa la impresión de la profundidad (= máximo nivel) del<br />

árbol al terminar de ingresar los datos.


12.5. Problemas Adicionales Pág. 131<br />

e) Ver que si los datos se ingresan ya ordenados, el árbol correspondiente se<br />

reduce básicamente a un arreglo unidimensional: dar un ejemplo con 5 caracteres<br />

distintos, donde la profundidad sea 4.<br />

✎ Para eliminar este problema, se han diseñado una serie de variantes,<br />

como árboles balanceados, árboles “B”, etc. Observar que sacar nodos<br />

de un árbol es un proceso bastante más complicado que sacar nodos<br />

de una lista (ver [16, Cap.4]). ✄<br />

El lector se preguntará porqué hemos incluido el tema de árboles binarios<br />

en este capítulo de “objetos combinatorios”: créase o no, en este capítulo<br />

ya habíamos visto y recorrido árboles binarios, aunque tenían una estructura<br />

particular que nos permitía ahorrar espacio.<br />

Porejemplo,enelproblema12.1 construíamos todas las cadenas de bits<br />

de longitud n o, equivalentemente, los subconjuntos de {1,...,n}. Dada una<br />

cadena c de bits de longitud k, podemos considerar el “hijo a izquierda” como<br />

la cadena c0 de longitud k + 1 que se obtiene de c agregando a la derecha un 0, y<br />

de la misma forma considerar el “hijo a derecha” como la cadena c1 de longitud<br />

k + 1 que se obtiene agregando un 1. En la función recursiva del problema 12.1<br />

el nivel que estábamos construyendo era precisamente k, y cuando llegábamos<br />

a k = n, imprimíamos la cadena.<br />

También hay “árboles” en los problemas 12.4 y 12.5, sólo que los nodos<br />

pueden tener más de dos hijos. A diferencia del problema de los subconjuntos o<br />

cadenas de bits, el “recorrido” del árbol (que no es binario) es más complicado<br />

porque la descripción de los nodos lo es.<br />

✎ La variable k del procedimiento llegardesde oenelprocedimientoponer<br />

indica también el nivel que estamos recorriendo. Las instrucciones “ k :=<br />

k - 1”y“falta[j] := true ”, respectivamente, hacen que vayamos “un<br />

nivel hacia arriba” antes de descender nuevamente, y por eso la técnica de<br />

“borrar las huellas” que usamos en ambos casos es conocida como backtracking<br />

o “rastreo inverso”.<br />

12.5. Problemas Adicionales<br />

Problema 12.7. Resolver el problema 12.1 sin usar recursión, usando que las<br />

cadenas de bits de longitud n pueden pensarse como los coeficientes en base 2<br />

de los números entre 0 y 2 n − 1. Sugerencia: parak =0,...,2 n − 1, construir<br />

la lista de coeficientes e imprimirla. ✄<br />

Problema 12.8. Hacer un programa que dados n, k ∈ N imprima todos los<br />

subconjuntos de {1, 2,...,n} que tienen exactamente k elementos. ✄<br />

Problema 12.9 (Problema del Viajante). Supongamos que un viajante<br />

tiene que recorrer n ciudades (exactamente) volviendo a la de partida, sin repetir<br />

su visita a ninguna (salvo la inicial), y que el costo de viajar desde la ciudad i<br />

a la ciudad j es cij ≥ 0. El problema del viajante es encontrar una permutación<br />

(a1,a2,...,an) de(1, 2,...,n) de modo que el costo total del recorrido, ca1a2 +<br />

ca2a3 + ···+ can−1an + cana1, seamínimo. Como se recorre un ciclo, es suficiente<br />

tomar a1 =1.<br />

a) Usando una variante del programa del problema 12.5, hacer un programa<br />

para encontrar una solución al problema cuando el costo cij es:


Pág. 132 Generando objetos combinatorios<br />

i) i + j.<br />

ii) |i − j|.<br />

iii) i × j.<br />

iv) dado como entero aleatorio entre 1 y 10.<br />

Sugerencia: definir una variable min de tipo “ real ” y un arreglo opt donde<br />

se guardarán el costo más chico obtenido y la permutación correspondiente,<br />

y<br />

<br />

poner<br />

<br />

inicialmente min a un valor muy grande e inalcanzable como 1 +<br />

i j cij. A medida que se van recorriendo las permutaciones, calcular<br />

el costo de la permutación y compararlo con min, cambiando min y opt<br />

acordemente. Tomar n pequeño, 4 o 5 al principio, mientras se prueba el<br />

programa, y no mayor que 10–12 en cualquier caso.<br />

b) Ver que en el caso i) del inciso anterior, el costo es el mismo para cualquier<br />

permutación.<br />

c) ¿Podrías decir cuál es el óptimo en el caso ii) para cualquier n? Sugerencia:<br />

podemos pensar que los puntos 1,...,n están sobre una recta y los costos<br />

son las distancias.<br />

✎ En los ejemplos i–iii) los costos son simétricos, i.e. cij = cji. Como recorrer<br />

un ciclo en uno u otro sentido no cambia el costo, estamos trabajando (al<br />

menos) el doble de lo necesario con la propuesta.<br />

El problema del viajante y otros similares son sumamente importantes y<br />

se aplican a problemas de recorridos en general. Por ejemplo, para “ruteo” de<br />

vehículos, máquinas para perforar o atornillar, circuitos impresos y “chips”,<br />

etc., donde el costo puede medirse en dinero, tiempo, longitud, etc. Consecuentemente,<br />

hay toda una área de las matemáticas y computación dedicada al<br />

estudio de su resolución eficiente. Hasta el momento no se conocen algoritmos<br />

verdaderamente buenos, resolviéndose exactamente el caso de unos pocos miles<br />

de “ciudades” y costos arbitrarios. Aunque se sospecha que no hay algoritmos<br />

“eficientes”, aún no se ha demostrado que no los hay (para esto, hay que desarrollar<br />

toda una teoría matemática de qué significa “eficiente”). El algoritmo<br />

propuesto acá esquizás el menos eficiente, pues analiza todas las soluciones<br />

posibles, y por eso se lo llama de búsqueda exhaustiva. ✄<br />

12.6. Comentarios Bibliográficos<br />

El programa arbolbinario está basado en las presentaciones de [16, pág. 210]<br />

y[10, pág. 153].


Capítulo 13<br />

Grafos y árboles<br />

Si tomamos un conjunto de ciudades y las carreteras que las unen, y representamos<br />

gráficamente a las ciudades como puntos y a las carreteras como<br />

segmentos o curvas uniendo esos puntos, obtenemos una figura similar a la 11.4,<br />

en la cual pensábamos que los segmentos eran calles y los puntos las intersecciones.<br />

La idea subyacente en ambos casos es la de grafo, unconjuntodevértices<br />

(las ciudades o intersecciones) que indicaremos por V , y un conjunto de aristas<br />

(las rutas o calles), E, cada una de las cuales queda definida por dos vértices.<br />

No sólo los grafos están relacionados con calles o rutas. En comunicaciones<br />

también podemos pensar que los vértices son computadoras y las aristas representan<br />

la posibilidad de que dos computadoras se conecten entre sí. O, saliendo<br />

de las comunicaciones, podemos pensar en un árbol genealógico, donde los vértices<br />

son las personas y las aristas relacionan padres con hijos. En fin, los ejemplos<br />

de aplicaciones de grafos son muy variadas, y muchas veces sorprendentes.<br />

Euler (1707–1783) fue uno de los más grandes y prolíficos matemáticos,<br />

y ya hemos mencionado su nombre en conexión con análisis (la constante de<br />

Euler), teoría de números (la función φ ylafórmula de Euler-Binet), aunque<br />

éstas son comparativamente contribuciones menores suyas. Fue tan grande su<br />

influencia en las matemáticas que se unificaron notaciones que el ideó, como<br />

la de π (= 3.14159 ...) (del griego “perypheria” o circunferencia), i (= √ −1)<br />

(por imaginario), y e (= 2.71828 ...) (del alemán “einhalt” o unidad).<br />

Entre otras tantas, Euler fue quien originó el estudio de teoría de grafos y<br />

la topología al resolver en 1736 el famoso problema de los puentes de Königsberg<br />

(hoy Kaliningrad, en Rusia), donde el río Pregel se bifurcaba dejando dos<br />

islas (y dos costas), y las islas y las costas se unían por siete puentes. Euler<br />

resolvió el problema, demostrando que no se podían recorrer todos los puentes<br />

pasando una única vez por ellos, demostrando el teorema que hoy llamamos<br />

“de grafos eulerianos” y que mencionamos en el problema 13.7.<br />

En este capítulo veremos algunas propiedades básicas y algunos algoritmos<br />

elementales para grafos. Nos contentaremos con dar una simple, y seguramente<br />

demasiado breve, descripción de los términos y propiedades que usaremos,<br />

dejando para los cursos de matemática discreta las definiciones rigurosas y las<br />

demostraciones.<br />

✎ Lamentablemente no hay una nomenclatura ni notación uniforme de los<br />

muchos conceptos asociados a grafos, de modo que las nuestras pueden<br />

diferir de las de otros autores.


Pág. 134 Grafos y árboles<br />

A fin de distinguir los vértices entre sí es conveniente darles nombres, pero<br />

para el uso computacional supondremos que si hay n vértices, éstos se denominan<br />

1, 2,...,n, i.e. V = {1, 2,...,n}. Más aún, casi siempre usaremos el nombre<br />

n para el cardinal de V .Asícomon es el “nombre oficial” de |V |, el“nombre<br />

oficial” para |E| es m.<br />

Sólo consideraremos grafos simples, para los que no hay una arista de un<br />

vértice en sí mismo, ni aristas “paralelas” uniendo los mismos vértices. En este<br />

caso, podemos relacionar n = |V | y m = |E|: sihayn elementos, hay c(n, 2) =<br />

n(n − 1)/2 subconjuntos de 2 elementos, de modo que m ≤ c(n, 2).<br />

Las aristas están formadas por un par de vértices, y como el orden no importa,<br />

indicaremos la arista que une el vértice a con el vértice b por {a, b}. Claro<br />

que decir que {a, b} ∈E es lo mismo que decir que {b, a} ∈E.<br />

Si e = {a, b} ∈E, diremosquea y b son vecinos o adyacentes, quea y b<br />

son los extremos de e, oquee incide sobre a (y b). A veces, un vértice no tiene<br />

vecinos —no hay aristas que inciden sobre él— y entonces decimos que es un<br />

vértice aislado.<br />

En la figura 13.1, mostramos un ejemplo de grafo donde n =6,<br />

E = {{1, 2}, {1, 3}, {2, 3}, {2, 6}, {3, 4}, {3, 6}, {4, 6}}.<br />

yporlotantom =7,yelvértice 5 es aislado.<br />

3<br />

2<br />

1<br />

4 5<br />

Figura 13.1: Un grafo con 6 vértices y 7 aristas, el vértice 5 es aislado.<br />

Siguiendo con la analogía de calles y rutas, es común hablar de camino,<br />

una sucesión de vértices (el orden es importante) de la forma (v1,v2,...,vk),<br />

donde {vi,vi+1} ∈E para i =1,...,k− 1. Un camino en principio puede tener<br />

vértices repetidos, y si se cierra sobre sí mismo de modo que v1 = vk, decimos<br />

que se trata de un camino cerrado, mientras que si no tiene vértices repetidos<br />

decimos que es un camino simple. Por ejemplo, en la figura 13.1, {3, 2, 6, 3, 4}<br />

es un camino, {1, 2, 3} es un camino simple y {4, 3, 2, 1, 3, 6, 4} es un camino<br />

cerrado. Claro que podemos describir un camino tanto por los vértices como por<br />

las aristas intermedias, y en vez de poner (v1,v2,...,vk) podríamos considerar<br />

({v1,v2}, {v2,v3},...,{vk−1,vk}).<br />

Un ciclo es un camino cerrado sin aristas repetidas (pero puede tener varios<br />

vértices repetidos). Por ejemplo, {4, 3, 2, 1, 3, 6, 4} es un ciclo en el grafo de la<br />

figura 13.1.<br />

De fundamental importancia es reconocer si un grafo es conexo, es decir, si<br />

existe un camino desde cualquier vértice a cualquier otro vértice. Por otra parte,<br />

6


si un grafo es conexo pero no tiene ciclos, decimos que es un árbol.<br />

Por ejemplo, el grafo de la figura 13.1 no es conexo, pues tiene un vértice<br />

aislado. En la figura 13.2.a) mostramos otro grafo no conexo, y en b) unárbol.<br />

a) grafo no conexo con 9 vértices y 9 aristas. b) árbol con 7 vértices.<br />

Figura 13.2: Un grafo no conexo y un árbol.<br />

Si además el grafo es conexo (y simple), como se puede unir un vértice con<br />

los n − 1 restantes, debe haber al menos n − 1 aristas. De modo que para un<br />

grafo (simple) conexo, m tiene que estar básicamente entre n y n2 .<br />

A veces se consideran grafos dirigidos o digrafos, en los que las aristas están<br />

orientadas, y por lo tanto se indican como (a, b), y se distingue entre (a, b) y<br />

(b, a). Nosotros no estudiaremos este tipo de grafos, aunque en realidad hemos<br />

trabajado con el grafo de la figura 11.4 como si fuera un digrafo: las calles sólo<br />

iban de sur a norte o de oeste a este.<br />

Dada su estructura, es más sencillo trabajar con árboles que con grafos.<br />

Como hemos dicho, un árbol es un grafo (simple) conexo y sin ciclos, pero hay<br />

muchas formas equivalentes de describirlo, algunas de las cuales enunciamos<br />

como teorema (que por supuesto creeremos):<br />

Teorema 13.1 (Caracterizaciones de árboles). Un grafo simple G =(V,E)<br />

con |V | = n es un árbol si y sólosisecumplealgunadelassiguientescondiciones:<br />

a) Para cualquier a, b ∈ V existe un único camino que los une.<br />

b) G es conexo y |E| = n − 1.<br />

c) G no tiene ciclos y |E| = n − 1.<br />

d) G es conexo, y si se agrega una arista entre dos vértices cualesquiera, se<br />

crea un único ciclo.<br />

e) G es conexo, y si se quita cualquier arista —pero no los vértices en los<br />

que incide— queda no conexo.<br />

A veces en un árbol consideramos un vértice particular como raíz, y miramos<br />

a los otros vértices como “descendientes” de la raíz: los que se conectan mediante<br />

unaaristaalaraíz son los “hijos”, los que se conectan con un camino de 2 aristas<br />

son los “nietos” y así sucesivamente. Dado que hay un único camino de la raíz<br />

a cualquier otro vértice, podemos clasificar a los vértices según niveles: laraíz<br />

tiene nivel 0, los hijos nivel 1, los nietos nivel 2, etc., como hemos visto al estudiar<br />

árboles binarios ordenados en la sección 12.4.<br />

Por supuesto, podemos pensar que los nietos son hijos de los hijos, los hijos<br />

padres de los nietos, etc., de modo que —en un árbol con raíz— hablaremos<br />

de padres, hijos, ascendientes y descendientes de un vértice. Habrá uno o más<br />

vértices sin descendientes, llamados hojas, mientras que la raíz será elúnico<br />

vértice sin ascendientes. También es común referirse al conjunto formado por<br />

un vértice (aunque el vértice no sea la raíz) y sus descendientes como una rama<br />

del árbol.<br />

Pág. 135


Pág. 136 Grafos y árboles<br />

13.1. Representación de grafos en la computadora<br />

Antes de meternos de lleno con los algoritmos, nos falta una “cosita” más:<br />

¿cómo guardar la información de un grafo en la computadora? Ya sabemos que<br />

los vértices son 1, 2,...,n, pero ¿y las aristas?<br />

Hay muchas formas de representar un grafo en general, y nosotros en este<br />

capítulo nos limitaremos a dos: dar la lista de aristas, y dar la matriz de adyacencias,<br />

una matriz cuyas entradas son sólo0o1ydemodoquelaentradaij<br />

es 1 si y sólo si {i, j} ∈E.<br />

La representación mediante matriz de adyacencias es cómoda, y relativamente<br />

fácil de entender. Quizás sería más eficiente —para los algoritmos que<br />

veremos— dar para cada vértice una lista de sus vecinos. Esta tercera forma<br />

puede implementarse mediante arreglos, pero es mucho más natural usar listas<br />

encadenadas que veremos en el problema 14.9.<br />

En lo que resta de este capítulo, supondremos las declaraciones:<br />

const<br />

MAXN = 20; (* maximo numero de vértices *)<br />

MAXM = 100; (* maximo numero de aristas *)<br />

type<br />

arreglodevertices = array[1..MAXN] of integer;<br />

tipoarista = record i, j: integer end;<br />

arreglodearistas = array[1..MAXM] of tipoarista;<br />

matrizNN = array[1..MAXN,1..MAXN] of integer;<br />

var<br />

ngrafo, mgrafo: integer;<br />

aristasgrafo: arreglodearistas;<br />

adyacencias: matrizNN;<br />

usando para las aristas una representación como arreglo de registros.<br />

Usualmente el ingreso de datos es más sencillo mediante la lista de aristas,<br />

pues la matriz de adyacencias tiene n2 elementos y en general m ≪ n2 (y siempre<br />

m ≤ n(n − 1)/2 para grafos simples). De cualquier modo, es conveniente tener<br />

amanoprocedimientosparalalecturaypasardeunaaotrarepresentación:<br />

Problema 13.2. En nuestros programas supondremos que ngrafo, elnúmero<br />

de vértices del grafo, es entrado explícitamente (recordando que los vértices<br />

serán entonces 1, 2,...,ngrafo).<br />

a) Hacer un procedimiento para leer las aristas ingresadas por terminal, donde<br />

cada arista consta de dos números enteros, formando un arreglo de longitud<br />

mgrafo.<br />

✎ Como es usual, supondremos que el usuario ingresa correctamente los<br />

datos: los vértices de las aristas son enteros entre 1 y ngrafo, nohay<br />

aristas repetidas o de la forma {i, i}, y no hay más de MAXM aristas.<br />

b) Considerando el procedimiento dearistasaadyacencias,<br />

procedure dearistasaadyacencias;<br />

var i, j, k: integer;


13.2. Recorriendo un grafo Pág. 137<br />

begin<br />

for i := 1 to ngrafo do<br />

for j := 1 to ngrafo do<br />

adyacencias[i,j] := 0;<br />

for k := 1 to mgrafo do<br />

with aristasgrafo[k] do begin<br />

adyacencias[i,j] := 1;<br />

adyacencias[j,i] := 1<br />

end<br />

end;<br />

hacer un programa que lea el número de vértices, las aristas (como en el inciso<br />

anterior), calcule la matriz de adyacencias, e imprima para cada vértice,<br />

los vértices que son adyacentes. Por ejemplo, si la entrada son las aristas<br />

{1, 5}, {2, 5} y {4, 2}, deberá imprimir<br />

Vertice Vecinos<br />

1 5<br />

2 4 5<br />

3<br />

4 2<br />

5 1 2<br />

c) Hacer un procedimiento que, dada la matriz de adyacencias, construya el<br />

arreglo de aristas (a fin de evitar repeticiones, es conveniente construir sólo<br />

aristas de la forma {i, j} con i


Pág. 138 Grafos y árboles<br />

Claro que si el grafo no es conexo, no será posible hacerlo. Por suerte, con<br />

un pequeño esfuerzo extra, los algoritmos que veremos también nos dirán si el<br />

grafo es conexo o no.<br />

Estos algoritmos se encuadran en la estructura del algoritmo visitar, que<br />

mostramos en “seudo-código” en el cuadro 13.1, donde con “←” denotamos la<br />

asignación y con la indentación implícitamente señalamos un par “begin-end” o<br />

“comienzo-fin” para agrupar instrucciones.<br />

Algoritmo visitar<br />

Entrada: un grafo G =(V,E), y un vértice i0 ∈ V .<br />

Salida: la lista de vértices que se pueden alcanzar desde i0 “visitados”.<br />

comienzo<br />

Q ←{i0};<br />

mientras Q = ∅ hacer<br />

sea i ∈ Q;<br />

sacar i de Q;<br />

“visitar” i;<br />

para todo j adyacente a i hacer<br />

si j no está “visitado”yj/∈ Q entonces agregar j a Q<br />

fin<br />

Cuadro 13.1: Esquema del algoritmo visitar.<br />

En el algoritmo se guardan en la cola Q los vértices que pueden visitarse, esto<br />

es, son adyacentes a algún vértice ya visitado pero aún no han sido visitados. Así,<br />

en todo momento del algoritmo habrá tresclasesdevértices: los ya visitados, los<br />

que están en la cola (y todavía no se han visitado), y los que no fueron visitados<br />

ni están en la cola (porque todavía no visitamos ninguno de sus vecinos).<br />

Como no queremos agregar a la cola un vértice ya agregado, debemos guardar<br />

información que nos diga si un vértice ha estado en la cola o no. Para los<br />

algoritmos que haremos será conveniente usar un arreglo padre, inicialmenteen<br />

0, y al incluir el vértice j en la cola porque es vecino del vértice i que estamos<br />

visitando, pondremos padre j = i (> 0). Así, la condición padre i > 0 indicaráque<br />

i se ha puesto alguna vez en la cola, aunque ya no esté.<br />

A medida que recorremos el grafo, las aristas que usamos y los vértices que<br />

visitamos van formando un árbol (1) que —cuando el grafo es conexo— se llama<br />

generador o de expansión del grafo, pues contiene a todos los vértices del grafo<br />

original y las aristas del árbol son también aristas del grafo original (pero pueden<br />

no ser todas).<br />

Por supuesto que si el grafo que queremos recorrer es ya un árbol, no se formará<br />

uno nuevo. Pero sí que podemos tomar al vértice i0, con el que empezamos<br />

el recorrido, como raíz, y el arreglo padre justamente nos dirá quién es el padre<br />

de cada vértice en el árbol que se forma.<br />

(1) Esto irá quedando más claro con los ejemplos. Es una propiedad que no demostraremos,<br />

¡como tantas otras!


13.3. Recorrido a lo ancho y en profundidad Pág. 139<br />

Pondremos padre i0 = i0 para indicar que i0 es justamente la raíz y no podemos<br />

seguir “más arriba”, de modo que también para i = i0 la condición<br />

padre i > 0 es equivalente a que el vértice i se ha incoporado alguna vez a la<br />

cola.<br />

13.3. Recorrido a lo ancho y en profundidad<br />

En el recorrido “a lo ancho” o “ancho primero”, visitamos primero el vértice<br />

i0, luego sus vecinos, luego los vecinos de los vecinos, etc., sin volver a visitar a<br />

alguien ya visitado. O sea que, pensando en el árbol que se formará, visitaremos<br />

primerolaraíz, después todos sus hijos, después todos sus nietos, etc., en el mismo<br />

orden en que los vamos encontrando. Es entonces conveniente implementar<br />

la cola Q como cola fifo (como en la sección 12.1): el que llegó antes sale antes.<br />

Problema 13.4 (Recorrido a lo ancho). Implementamos el algoritmo en el<br />

programa anchoprimero (pág. 199), y observamos que:<br />

• Además de las variables mencionadas en la sección 13.1, incorporamos<br />

nvisitado y marbol que nos indicarán la cantidad de vértices visitados<br />

y aristas en el árbol que forman. Por supuesto, al finalizar tendremos<br />

nvisitado = ngrafo si y sólo si hemos recorrido todos los vértices, o equivalentemente,<br />

si el grafo original es conexo.<br />

También agregamos el arreglo aristasarbol , donde guardaremos las aristas<br />

del árbol construido, y el arreglo de vértices visitado, donde guardaremos<br />

el orden en que han sido visitados los vértices.<br />

Es posible que sólo querramos saber si el grafo es conexo. En este caso<br />

los arreglos aristasarbol y visitado no son necesarios, y sólo habrá que<br />

mantener nvisitado y compararlo al terminar con el valor de ngrafo.<br />

• El procedimiento ancho es el que realiza el trabajo. Hemos tomado como<br />

vértice inicial i0 = 1, guardando la cola (fifo) con los vértices a visitar en<br />

el arreglo avisitar,entreppo y fin.<br />

Cuando visitamos un vértice i, guardamos el número correlativo de visita<br />

en visitado. Luego recorremos los vecinos, reconocidos por la condición<br />

adyacencias ij > 0. Si un vecino j de i tiene padre j = 0, quiere decir que<br />

no ha sido agregado a la cola, y entonces se lo coloca en la cola y actualiza<br />

el valor de padre j .<br />

• A fin de reconocer las aristas del árbol, usamos el arreglo padre en el<br />

procedimiento hacerarbol.<br />

a) Ejecutar el programa, tomando como entrada el grafo de la figura 13.1,<br />

agregando las aristas {3, 5} y {4, 5}. Losvértices se recorren en el orden<br />

1, 2, 3, 6, 4, 5.<br />

b) Cambiar el programa de modo de que la raíz sea un vértice arbitrario r (en<br />

vez de 1), ingresado por el usuario.<br />

✎ Una fuente de ineficiencia en la implementación es el uso de la matriz de<br />

adyacencias para reconocer a los vecinos. Esto hace que nuestro programa<br />

haga del orden de n 2 pasos para recorrer el grafo. Si m ≪ n 2 ,esmás conveniente<br />

usar directamente una lista de vecinos para cada vértice, puesto<br />

que entonces el algoritmo hará delordendempasos.


Pág. 140 Grafos y árboles<br />

✎ El orden en que se recorren los vértices —tanto en ancho primero como en<br />

profundidad primero que veremos luego— está determinado también por<br />

el orden que se dan a los vecinos. Acá, al usar la matriz de adyacencias<br />

para buscar los vecinos, seguimos el orden de los naturales, pero podría ser<br />

otro. En la mayoría de las aplicaciones, la numeración dada a los vértices<br />

y sus vecinos no es importante: si lo fuera, hay que sospechar del modelo. ✄<br />

En general, aún cuando el grafo sea un árbol binario ordenado (y con raíz<br />

1), el recorrido a lo ancho es distinto de los recorridos “en orden”, “pre orden” o<br />

“post orden” que hemos visto en el problema 12.6, pues ahora visitamos los vecinos<br />

de la raíz, luego los vecinos de los vecinos, etc., en otras palabras, visitamos<br />

el árbol por niveles.<br />

Problema 13.5. Agregar un procedimiento al programa arbolbinario para imprimir<br />

el árbol construido por niveles: primero la información del vértice en el<br />

nivel 0, luego la de los vértices en el nivel 1, etc., hasta la profundidad. ✄<br />

Si en el algoritmo visitar, en vez de implementar la cola como fifo, la implementamos<br />

como lifo (pila), visitaremos primero los vértices que se han incorporando<br />

más recientemente a la pila. Si el grafo fuera ya un árbol, resultará que<br />

primero visitaremos toda una rama hasta el fin antes de recorrer otra (2) ,loque<br />

hace que este tipo de recorrido se llame “en profundidad” o de “profundidad<br />

primero”.<br />

Problema 13.6 (Recorrido en profundidad).<br />

a) Cambiar el procedimiento ancho del programa anchoprimero de modo de<br />

implementar una pila (cola lifo) en vez de una cola fifo, para realizar el<br />

recorrido en profundidad. Si la entrada es la misma que en el problema 13.4<br />

(agregando las aristas {3, 5} y {4, 5}), los vértices se visitan en el orden<br />

1, 3, 6, 5, 4, 2.<br />

b) Algunos autores (por ejemplo [8]) consideran que la búsqueda en profundidad<br />

consiste en quitar el vértice de la cola sólo cuando se han visitado todos<br />

los vecinos que están después en la cola, lo que en el ejemplo anterior daría<br />

por resultado el orden de visita 1, 2, 3, 4, 5, 6.<br />

Hacer un programa con esta nueva variante, reemplazando en los lugares<br />

apropiados las instrucciones por:<br />

(* al principio solo esta 1 *)<br />

padre[1] := 1;<br />

hay := 1; (* la cantidad de vertices en la pila *)<br />

avisitar[1] := 1;<br />

nvisitado := 1; visitado[1] := 1;<br />

while (hay > 0) do (* mientras la pila no es vacia *)<br />

begin<br />

i := avisitar[hay]; (* tomar el ultimo vertice *)<br />

(* y buscar vecino que no tenga padre *)<br />

j := 1; buscar := true;<br />

while (buscar) do begin<br />

if ((adyacencias[i,j] > 0) and (padre[j] = 0))<br />

(2) Bah, que nos vamos por las ramas.


13.4. Camino más corto: Dijkstra Pág. 141<br />

then<br />

(* se encontro *)<br />

begin<br />

buscar := false;<br />

(* agregar arista (i,j) al arbol *)<br />

padre[j] := i;<br />

(* registrar el orden de visita *)<br />

nvisitado := nvisitado + 1;<br />

visitado[nvisitado] := j;<br />

(* y agregar j a la cola *)<br />

hay := hay + 1;<br />

avisitar[hay] := j<br />

end<br />

else (* probar con el siguiente *)<br />

j := j + 1;<br />

if (j = ngrafo + 1) then begin<br />

(* todos los vecinos ya tienen padre *)<br />

buscar := false;<br />

(* sacar i de la pila *)<br />

hay := hay - 1<br />

end<br />

end (* buscar *)<br />

end (* hay *)<br />

donde la variable buscar es booleana.<br />

c) Hacer un gráfico de los árboles obtenidos en cada caso para el ejemplo dado<br />

y compararlos.<br />

✎ Si el grafo a recorrer es ya un árbol, los resultados obtenidos por estas dos<br />

variantes es el mismo. Pero nuestra implementación de la segunda variante<br />

es mucho más ineficiente que la primera, pues recorremos varias veces las<br />

adyacencias de un mismo vértice buscando un vecino para agregar.<br />

Recordar también la nota sobre la numeración de los vecinos al final<br />

del problema 13.4. ✄<br />

Problema 13.7. Un célebre teorema de Euler dice que un grafo tiene un ciclo<br />

que pasa por todas las aristas exactamente una vez, llamado ciclo de Euler,<br />

si y sólo si el grafo es conexo y el grado de cada vértice es par (recordar el<br />

problema 13.3).<br />

Modificar el programa anchoprimero para que a la salida determine también<br />

si el grafo tiene o no un ciclo de Euler usando el teorema.<br />

✎ Un problema muy distinto es encontrar un ciclo de Euler en caso de existir.<br />

Esto es lo que hacemos en el problema 14.10. ✄<br />

13.4. Camino más corto: Dijkstra<br />

Algunas veces la información interesante de un grafo está enlosvértices,<br />

y las aristas nos dicen que, por alguna razón, dos vértices están relacionados<br />

ynadamás. Tal es el caso de los árboles binarios de la sección 12.4, donde la<br />

conexión estaba asociada al orden entre los datos guardados en los vértices.


Pág. 142 Grafos y árboles<br />

Otras veces, las aristas tienen información adicional que queremos considerar.<br />

Por ejemplo si las aristas indican rutas entre ciudades, podría ser la distancia<br />

entre las ciudades. Cuando cada arista e ∈ E de un grafo tiene un costo o peso<br />

asociado, we, decimos que se trata de un grafo con pesos o pesado (nosotros sólo<br />

consideraremos el caso we > 0paratodoe ∈ E).<br />

En la figura 13.3 damos un ejemplo de grafo con pesos, marcados con recuadros<br />

en las aristas correspondientes, pero por comodidad reproducimos los<br />

datos en el cuadro al costado.<br />

1<br />

2<br />

3<br />

2<br />

1<br />

1<br />

2<br />

1<br />

3<br />

2<br />

8<br />

4 5<br />

1<br />

3<br />

6<br />

Arista e peso we<br />

{1,2} 2<br />

{1,4} 3<br />

{1,5} 8<br />

{2,3} 2<br />

{2,4} 1<br />

{3,4} 1<br />

{3,5} 2<br />

{3,6} 1<br />

{4,6} 1<br />

{5,6} 3<br />

Figura 13.3: Un grafo con pesos en las aristas.<br />

Si tuviéramos que ir de una ciudad a otra, teniendo distintas rutas alternativas<br />

para elegir, es razonable preguntarse cuál de ellas será lamás corta (o la<br />

más barata). Éste es el problema del camino más corto: enungrafopesado,y<br />

dados dos vértices s, t ∈ V , encontrar un camino (v0 = s, v1,...,vk = t) con<br />

peso total mínimo, donde el peso total de un camino se define como<br />

w {v0,v1} + w {v1,v2} + ···+ w {vk−1,vk} =<br />

k<br />

w {vi−1,vi}.<br />

Observar que el valor de k no está fijo: no nos interesa si tenemos que usar<br />

una arista o cien, sólo nos interesa que la distancia total para ir de s a t sea<br />

mínima.<br />

Por ejemplo, en el grafo de la figura 13.3 podemos usar varios caminos para<br />

ir del vértice 1 al 5: el camino (1, 5) usa una única arista (k = 1) y tiene peso 8,<br />

el camino (1, 2, 3, 5) usa 3 aristas y tiene costo total 2 + 2 + 1 = 5, y en realidad<br />

no hay otro con menor costo.<br />

Tal vez el algoritmo más conocido para resolver este problema sea el de<br />

Dijkstra, que sigue la estructura del algoritmo visitar: se comienza desde un<br />

vértice, en este caso s, se lo coloca en una cola, y se visitan los vértices de la<br />

cola.<br />

E. W. Dijkstra (1930–2002) nació ymurióenHolanda. Fue uno de los más<br />

grandes intelectos que contribuyeron a la lógica matemática subyacente en los<br />

programas de computación y sistemas operativos. Entre sus muchas y destacadas<br />

contribuciones está el algoritmo para el camino más corto que presentamos,<br />

publicado en 1959.<br />

Nuevamente habrá tresclasesdevértices: los visitados, los que están en la<br />

cola y no han sido aún visitados, y los que nunca estuvieron en la cola. Como<br />

i=1


13.4. Camino más corto: Dijkstra Pág. 143<br />

novedad, para cada i ∈ V consideraremos el valor di de la distancia más corta de<br />

s a i mediante caminos de la forma (s, v1,v2,...,vk,i) donde todos los vértices<br />

excepto i ya han sido visitados. A fin de indicar que no existe tal camino,<br />

pondremos di = ∞ (siendo éste el valor inicial para todo i).<br />

A diferencia del recorrido a lo ancho, donde se visitan los vértices en el orden<br />

de llegada a la cola, en el algoritmo de Dijkstra elegimos para visitar el vértice<br />

de la cola con menor distancia di. El algoritmo termina cuando t es el próximo<br />

vértice a visitar (y entonces dt es la distancia del menor camino de s a t), o<br />

cuando la cola es vacía,encuyocasonoexisteuncaminodesdeshacia t (y<br />

necesariamente el grafo no es conexo).<br />

Al visitar un vértice i yexaminarunvértice vecino j, verificamos si<br />

di + w {i,j} 0paratodoe∈E. Nosotros dejaremos estas<br />

propiedades para cursos de matemática discreta o teoría de grafos.<br />

Podemos esquematizar el algoritmo en seudo-código como en el cuadro 13.2.<br />

Algoritmo de Dijkstra<br />

Entrada: un grafo pesado G =(V,E,W), y dos vértices s, t ∈ V .<br />

Salida: la distancia dt de un camino más corto entre s y t (dt =<br />

∞, sitalcaminonoexiste).<br />

comienzo<br />

para todo i ∈ V hacer di ←∞;<br />

ds ← 0; Q ←{s};<br />

repetir<br />

sea i ∈ Q tal que di =mínj∈Q dj;<br />

si i = t entonces<br />

sacar i de Q;<br />

para todo j adyacente a i hacer<br />

si di + w {i,j}


Pág. 144 Grafos y árboles<br />

∞, ycomolosvértices j que no han ingresado en la cola son los que satisfacen<br />

dj = ∞, elúltimo lazo interno puede simplificarse a<br />

para todo j ∈ V hacer<br />

si di + w {i,j}


13.5. Mínimo árbol generador: Prim Pág. 145<br />

4. Una vez que determinamos el vértice a visitar, i, lo comparamos con t,<br />

el vértice al cual queremos llegar. Si i = t, hemos llegado y se termina<br />

el procedimiento.<br />

5. En cambio, si i = t, recorremos los vecinos de i, indicados con j,<br />

actualizando dist, padre y la cola si fuera necesario, de acuerdo a la<br />

desigualdad (13.1). Antes de cambiar dist j, sin embargo, preguntamos<br />

si j ha estado en la cola comparando dist j con infinito.<br />

• Para construir e imprimir un camino mínimo, usamos el arreglo padre en<br />

el procedimiento hacercamino: formamos un arreglo recorriendo los antecesores<br />

de t hasta llegar a s. El problema es que, como vamos agregando<br />

atrás, nos queda el camino (t, padre t,...,s), en orden inverso.<br />

Ésto puede no ser problema, pero es fácil imprimirlo de atrás para adelante<br />

de modo de mostrarlo “al derecho”, como hemos hecho. Si fuera<br />

necesario, se puede invertir el arreglo usando un procedimiento como el<br />

del problema 8.3.<br />

a) Ejecutar el programa, tomando como entrada el grafo pesado de la figura<br />

13.3, vértice de partida 1 y vértice de llegada 4.<br />

b) Cambiar el programa de modo que calcule las distancias mínimas de s (ingresado<br />

por el usuario) a todos los otros vértices del grafo (sólo las distancias,<br />

no los caminos).<br />

c) Modificarlo de modo que calcule todas las distancias de i a j, para1≤i< j ≤ ngrafo.<br />

✎ Técnicamente el tipo de cola que usamos en los programas dijkstra y prim<br />

—en la próxima sección— se llama cola de prioridad, en vez de “lifo” o<br />

“fifo”: no se elige ni el primero ni el último sino que se usa otro criterio<br />

“de valor” o “prioridad” para sacar un elemento de la cola.<br />

Nuestra implementación de este tipo de colas es un tanto rudimentaria<br />

a fin de mantener la claridad de los programas. El uso de la variante de<br />

selección directa tarda del orden de |V | pasos cada vez que se elige el<br />

próximo vértice a visitar. En cambio, el uso de variantes de algoritmos<br />

de clasificación más avanzados hace que se tarde a lo sumo del orden de<br />

log2 (|V |) pasos para esa elección.<br />

✎ Otra fuente de ineficiencia es el uso de la matriz de adyacencias (o costos)<br />

si m ≪ n 2 , como hemos observado en el problema 13.4.<br />

✎ Hemos tratado de mantener la presentación de los algoritmos y programas<br />

de este capítulo — algoritmo visitar y los programas anchoprimero, dijkstra<br />

y prim— lomásparecidas entre sí a fin de resaltar las semejanzas, lo que<br />

también afecta (un poquito) a la eficiencia. Es muy posible que el lector<br />

los vea con un “disfraz” bastante distinto en otras referencias. ✄<br />

13.5. Mínimo árbol generador: Prim<br />

Si tuviéramos una cierta cantidad de ciudades y las distancias entre ellas,<br />

un problema interesante es ver cómo construir carreteras de modo de que todas<br />

las ciudades puedan unirse entre sí mediante estas nuevas rutas. Dado que no<br />

nos interesan ciclos, pero el grafo formado por las carreteras debe ser conexo,<br />

estamos buscando un árbol generador.


Pág. 146 Grafos y árboles<br />

El teorema 13.1 nos dice que cuando tenemos un grafo conexo que no es<br />

un árbol, tendremos más de un árbol generador: será cuestión de tomar un<br />

ciclo en el grafo original, sacar cualquiera de las aristas del ciclo, y repetir el<br />

procedimiento hasta que no haya más ciclos, manteniendo la conexión. Como<br />

las aristas que sacamos son bastante arbitrarias, en general hay muchos árboles<br />

generadores de un mismo grafo.<br />

Cuando hay pesos (o costos) asociados a las aristas, como en el caso de la<br />

construcción de las carreteras, nos interesa encontrar entre todos los árboles generadores<br />

uno que minimice la suma de los pesos de las aristas que lo componen,<br />

<br />

we,<br />

e en el árbol<br />

llamado árbol generador mínimo (es posible que haya más de un árbol con esta<br />

propiedad, por ejemplo si los costos de todas las aristas son 1).<br />

Volviendo al grafo de la figura 13.3, podemos formar un árbol generador con<br />

las aristas {1, 2}, {1, 4}, {2, 3}, {3, 6}, {6, 5} (siempre un árbol generador debe<br />

tener n − 1 aristas), con peso total 2 + 3 + 2 + 1 + 3 = 11, y si reemplazamos la<br />

arista {5, 6} por la arista {3, 5}, reducimos el costo en 1.<br />

Hay varios algoritmos para encontrar un árbol generador mínimo, y nosotros<br />

veremos aquí el debido a Prim, pues sigue la estructura del algoritmo visitar,<br />

siendo por lo tanto muy parecido al recorrido a lo ancho o al algoritmo de<br />

Dijkstra para el camino más corto. Otro algoritmo, más eficiente en la mayoría<br />

de los casos prácticos, es el de Kruskal, que dejamos para la sección de Problemas<br />

Adicionales pues su implementación es más elaborada.<br />

El algoritmo de Kruskal fue publicado en 1956, mientras que el de Prim fue<br />

publicado en 1959. Dijkstra también obtuvo en forma independiente el algoritmo<br />

de Prim y lo publicó en 1959, en el mismo trabajo donde presenta su algoritmo<br />

para el camino más corto, lo que no es sorpresa dada la similitud.<br />

Recordemos que en el algoritmo visitar manteníamos una cola con los vértices<br />

a visitar, y se formaban tres clases de vértices: los visitados, los que estaban<br />

en la cola, y los que nunca habían ingresado en la cola.<br />

En el algoritmo de Prim se sigue la misma idea, sólo que un vértice en<br />

la cola se visita cuando su “distancia” a los vértices ya visitados es la menor<br />

entre los vértices de la cola. De modo que hay que hacer pocas modificaciones al<br />

algoritmo de Dijkstra: cambiar la definición de la distancia y, en vez de construir<br />

el camino, construir el árbol correspondiente. A diferencia del algoritmo de<br />

Dijkstra, tenemos que mantener información sobre los vértices visitados. Como<br />

el arreglo padre se irá modificando en los sucesivos pasos (en el de Dijkstra<br />

también, pero no en el recorrido a lo ancho), introducimos un nuevo vector con<br />

valores lógicos visitado.<br />

✎ Como con el algoritmo de Dijkstra, se necesita que we > 0paratodoe ∈ E.<br />

Asimismo, dejamos la demostración de que el algoritmo da efectivamente<br />

un árbol generador mínimo para los cursos de matemática discreta o teoría<br />

de grafos.<br />

Problema 13.9 (Algoritmo de Prim para el mínimo árbol generador).<br />

A fin de implementar el algoritmo de Prim, observamos las similitudes y diferencias<br />

con el de Dijkstra:<br />

• La matriz de costos y el valor infinito son idénticos.


13.5. Mínimo árbol generador: Prim Pág. 147<br />

• En el algoritmo de Prim “no existen” s y t, ni por supuesto el camino que<br />

los une.<br />

• Mientras que en el programa dijkstra tomamos a s como “raíz”, en el<br />

programa prim el vértice 1 es el primero en ingresar a la cola, como en el<br />

programa anchoprimero.<br />

• Guardamos información sobre si un vértice ha sido visitado o no en el<br />

arreglo booleano visitado.<br />

✎ Observar que es diferente al usado en el programa anchoprimero, en<br />

el cual guardábamos en visitado el orden de visita.<br />

La distancia se actualiza comparando dist j con w {i,j} envezdeconw {i,j}+<br />

dist i. Por lo tanto, el procedimiento mascorto del programa dijkstra se<br />

puede cambiar a<br />

procedure arbolminimo;<br />

var<br />

i, j, k, kmin, hay: integer;<br />

d, dmin: costo;<br />

avisitar: arreglodevertices;<br />

visitado: array[1..MAXN] of boolean;<br />

begin<br />

(* inicializacion *)<br />

dearistasacostos;<br />

for i := 1 to ngrafo do begin<br />

padre[i] := 0;<br />

dist[i] := infinito;<br />

visitado[i] := false<br />

end;<br />

(* 1 es la "raiz" *)<br />

padre[1] := 1; dist[1] := 0;<br />

(* cola inicial *)<br />

hay := 1; avisitar[1] := 1;<br />

repeat (* aca hay > 0 *)<br />

(* nuevo i: el de minima distancia en la cola *)<br />

kmin := hay; i := avisitar[hay]; dmin := dist[i];<br />

for k := 1 to hay - 1 do begin<br />

j := avisitar[k]; d := dist[j];<br />

if (d < dmin) then begin<br />

kmin := k; i := j; dmin := d end<br />

end;<br />

(* poner i al final de la cola y sacarlo *)<br />

if (kmin < hay) then<br />

avisitar[kmin] := avisitar[hay];<br />

hay := hay - 1;


Pág. 148 Grafos y árboles<br />

(* la distancia de i ya no debe modificarse *)<br />

visitado[i] := true;<br />

(* examinar vecinos de i *)<br />

for j := 1 to ngrafo do<br />

if ((not visitado[j]) and<br />

(costos[i,j] < dist[j])) then begin<br />

(* si j no se agrego, agregarlo a la cola *)<br />

if (dist[j] = infinito) then begin<br />

hay := hay + 1; avisitar[hay] := j end;<br />

(* actualizar dist y padre *)<br />

dist[j] := costos[i,j];<br />

padre[j] := i<br />

end<br />

until (hay = 0)<br />

end;<br />

• En dijkstra buscábamos un camino, y ahora debemos construir un árbol,<br />

i.e. encontrar las aristas correspondientes. Esto lo podremos hacer usando<br />

nuevamente padre, agregando los pesos mediante dist. Así, el procedimiento<br />

hacercamino en dijkstra puede reemplazarse por una variante del<br />

procedimiento hacerarbol del programa anchoprimero, observando que el<br />

peso w de la arista {k, padre k } es dist k.<br />

• A la salida, debemos imprimir el árbol encontrado y un cartel en caso que<br />

el grafo original no sea conexo. Esto puede hacerse modificando las partes<br />

correspondientes de los programas anchoprimero o dijkstra.<br />

a) Hacer un programa para implementar el algoritmo de Prim usando las<br />

ideas expuestas. En el grafo de la figura 13.3, se obtiene el árbol de aristas<br />

{1, 2}, {3, 4}, {2, 4}, {3, 5}, {4, 6}.<br />

b) Incluir también instrucciones a fin de imprimir el peso total del árbol resultante.<br />

c) El algoritmo expuesto empieza con el vértice 1. Cambiarlo de modo de poder<br />

comenzar con un vértice arbitrario r ingresado por el usuario. Observar que<br />

se pueden obtener distintos árboles de mínimo costo (aunque siempre con<br />

el mismo costo total).<br />

✎ Dado la similitud entre nuestras implementaciones de los algoritmos de<br />

“ancho primero”, Dijkstra y Prim, no es sorprendente que valgan las mismas<br />

observaciones hechas al final de los problemas 13.4 y 13.8. ✄<br />

13.6. Problemas Adicionales<br />

Problema 13.10. Modificar el programa anchoprimero de modo que al terminar,<br />

además de decidir si el grafo es conexo o no, decida<br />

a) si es un árbol o no,<br />

b) si tiene un ciclo o no,<br />

c) y en ese caso, exhibir un ciclo.<br />

Sugerencia: incorporar las modificaciones una por vez. ✄


13.6. Problemas Adicionales Pág. 149<br />

13.6.1. El algoritmo de Kruskal<br />

El algoritmo de Kruskal para encontrar un árbol generador de mínimo peso<br />

se aparta un poco del esquema del algoritmo visitar, ymás bien se acerca al<br />

denominado algoritmo del codicioso pues busca incorporar la mejor arista de las<br />

que quedan.<br />

Antes de describir el algoritmo es conveniente introducir el concepto de componente<br />

conexa (o simplemente componente cuando quede claro que es la conexa):<br />

dado un grafo G =(V,E), vamos a dividir, o más correctamente, particionar,<br />

elconjuntodevértices V en subconjuntos, digamos A1,A2,...,Ac, que<br />

llamaremos componentes conexas de G, de modo que dos vértices u, v ∈ V están<br />

en la misma componente si (y sólo si) existe un camino de u a v. Deestaforma<br />

resulta que las componentes A1,A2,...,Ac satisfacen:<br />

1. V = ∪1≤i≤cAi.<br />

2. Si c>1, Ai ∩ Aj = ∅ para 1 ≤ i


Pág. 150 Grafos y árboles<br />

Algoritmo de Kruskal<br />

Entrada: un grafo G con vértices V ,aristasE ypesos{we}e∈E.<br />

Salida: las componentes conexas (vértices) de G, C, y un conjunto<br />

F ⊂ E tal que H =(V,F) tiene las mismas componentes<br />

conexas que G, y con el mínimo peso entre todos los<br />

grafos con esta propiedad. En particular si G es conexo,<br />

H es un árbol generador de mínimo peso.<br />

comienzo<br />

para todo i ∈ V hacer Ci ←{i};<br />

C←{Ci}i∈V ;<br />

F ←∅;<br />

Q ← E;<br />

mientras (E = ∅) y(|C| > 1) hacer<br />

sea e = {i, j} una arista de menor peso en Q;<br />

sacar e de Q;<br />

si Ci = Cj entonces<br />

C ← Ci ∪ Cj;<br />

eliminar Ci y Cj de C y agregar C a C;<br />

Ci ← C; Cj ← C;<br />

F ← F ∪{e}<br />

fin<br />

Cuadro 13.3: Esquema del algoritmo de Kruskal.<br />

una estructura eficiente para ello que —conservando el inglés— llamaremos<br />

union-find (por “unión-encontrar”) y que pasamos a describir.<br />

En cada etapa del algoritmo, vamos a considerar para cada componente<br />

C ∈Cun vértice r ∈ C representante de C, y una función f : V → V tal que<br />

f(i) es el representante de la componente en la que está i, Ci. Enparticular,si<br />

r es el representante de C, tendremos f(r) =r.<br />

f irá cambiando en cada etapa, pues se van fusionando las componentes<br />

conexas, pero como inicialmente tenemos Ci = {i} para todo i ∈ V ,inicialmente<br />

tendremos f(i) =i.<br />

Cuando en alguna etapa se unen C y C ′ —con representantes r y r ′ respectivamente—<br />

para formar C ′′ , por simplicidad elegiremos a uno de ellos, digamos<br />

r, como representante de C ′′ . Es decir que al finalizar esa etapa f(r) sigue siendo<br />

r pero f(r ′ )=r, y en realidad f se ha modificado en todos los vértices de C ′ .<br />

Hacer la redefinición de f explícitamente en cada etapa para todos los vértices<br />

es muy costosa, por lo que se considera otra función auxiliar, g, que inicialmente<br />

toma los mismos valores de f, i.e. inicialmente g(i) =i para todo<br />

i ∈ V .<br />

g también se va modificando en cada etapa, pero lo haremos únicamente<br />

sobre el representante que ha dejado de serlo. Con las notaciones anteriores de<br />

C, C ′ , r y r ′ , y habiendo elegido a r como representante de C ∪ C ′ , pondríamos<br />

g(r ′ )=r. Observemos que una vez que un vértice i deja de ser representante<br />

de la clase Ci, el valor g(i) nosemodifica(g sólo se modifica sobre algunos


13.6. Problemas Adicionales Pág. 151<br />

representantes). Es decir, o bien g(i) no se modifica nunca, o bien se modifica<br />

una única vez.<br />

Para encontrar f(i) en cualquier momento, preguntamos si g(i) =i. Encaso<br />

afirmativo, quiere decir que, o bien la componente Ci no se unió con ninguna<br />

otra, o bien cada vez que se unió con otra resultó que elegimos a i como<br />

representante de la nueva componente, y debe ser f(i) =i.<br />

Por otro lado, si g(i) =j = i, quiere decir que en algún momento Ci se<br />

unióconCjy tomamos como representante a j en vez de i,yporlotantoCi = Cj<br />

desde ese momento en adelante. Podría ser que g(j) = j, en cuyo caso repetimos<br />

el argumento, considerando los valores sucesivos g(i),g(g(i)),g(g(g(i))),...<br />

hasta llegar a algún valor k para el cual g(k) =k, que debe ser el representante<br />

de la clase, por lo que f(i) =k.<br />

✎ El uso de g es análogo al del arreglo padre en el procedimiento hacercamino<br />

del programa dijkstra. De hecho, estamos pensando a cada componente<br />

conexa como árbol con raíz, que se reconoce por g(r) =r y las aristas<br />

son de la forma {i, g(i)} con g(i) = i. Sin embargo, las aristas {i, g(i)} no<br />

tienen por qué estar en el conjunto de aristas originales E.<br />

En la implementación, llamaremos find (por “encontrar”) a este proceso<br />

iterativo para calcular la función f, guardaremos los valores de g en el arreglo<br />

repre (por “representante”, claro), que por simplicidad supondremos definido<br />

globalmente, y pondremos<br />

function find(i: integer): integer;<br />

var r: integer;<br />

begin<br />

r := i;<br />

while (r repre[r]) do r := repre[r];<br />

find := r<br />

end;<br />

Ahora tenemos que resolver cómo elegir el representante cuando se unen<br />

las componentes C y C ′ con representantes r y r ′ respectivamente, o en otras<br />

palabras, cómo actualizar g (o el arreglo repre). En principio podríamos elegir<br />

cualquiera de g(r) =r ′ o g(r ′ )=r, pero podemos hacer la elección eficientemente<br />

si tenemos en cuenta que g se usará para buscar, dado i, alrepresentante<br />

de Ci con el procedimiento iterativo de find, y nos interesa mantener el número<br />

de iteraciones lo más bajo posible.<br />

Si una componente C tiene como representante a r, llamemos ℓ(r) alnúmero<br />

máximo de iteraciones (del lazo “ while ” en el procedimiento find) paracualquier<br />

vértice de esa componente C. Inicialmente tendremos ℓ(r) =0paratodo<br />

r, pero este valor se irá modificando a medida que fusionemos componentes.<br />

Cuando se juntan las componentes C y C ′ con representantes r y r ′ , y<br />

elegimos como representante de la nueva clase a (por ejemplo) r, elnúmero de<br />

iteraciones máximas para la nueva componente será:<br />

<br />

ℓ(r ′ ) + 1 para los vértices cuyo representante era r ′ ,<br />

ℓ(r) para los vértices cuyo representante era r,<br />

de modo que el nuevo valor de ℓ(r) serámáx{ℓ(r ′ )+1,ℓ(r)}.<br />

Si queremos que los valores de ℓ aumenten lo menos posible, podemos elegir<br />

r o r ′ como nuevo representante comparando máx{ℓ(r ′ )+1,ℓ(r)} ymáx{ℓ(r)+


Pág. 152 Grafos y árboles<br />

1,ℓ(r ′ )}, o equivalentemente, ℓ(r) yℓ(r ′ ):<br />

<br />

g(r ′ )=g(r) si ℓ(r ′ )


13.6. Problemas Adicionales Pág. 153<br />

• Para la salida necesitaremos un procedimiento para imprimir las aristas<br />

elegidas, similar al de escribirarbol en el programa anchoprimero.<br />

• Luego habrá que declarar las funciones de “union-find”, como hemos descripto<br />

anteriormente.<br />

• Podemos hacer el trabajo principal en el procedimiento arbolminimo, donde<br />

elegimos la arista de menor peso haciendo parcialmente selección directa<br />

(como hicimos en dijkstra al elegir la menor distancia entre los que estaban<br />

en la cola).<br />

No es necesario mantener una cola explícita para las aristas si se usa el<br />

mismo arreglo de aristas originales (en todo caso, si es necesario mantener<br />

la lista original, se puede hacer una copia, o directamente pasar el arreglo<br />

de aristas como argumento con lo que la copia se hace automáticamente).<br />

En esta implementación, no “se pierden” las aristas originales, sino que<br />

cambian de lugar (quedando parcialmente ordenadas de mayor a menor).<br />

procedure arbolminimo;<br />

var<br />

hay, ri, rj, k, kmin: integer;<br />

wmin : costo;<br />

e: tipoarista;<br />

begin<br />

(* inicializacion *)<br />

inicializaruf; (* inicializar union-find *)<br />

nconexas := ngrafo; (* numero de componentes *)<br />

marbol := 0; (* en el bosque no hay aristas *)<br />

hay := mgrafo; (* cantidad de aristas a examinar *)<br />

while ((nconexas > 1) and (hay > 0)) do begin<br />

(* tomar la arista de menor peso<br />

y sacarla de la pila *)<br />

kmin := hay;<br />

e := aristasgrafo[hay];<br />

wmin := e.w;<br />

for k := 1 to hay - 1 do<br />

with aristasgrafo[k] do<br />

if (w < wmin) then begin<br />

kmin := k; wmin := w end;<br />

if (kmin < hay) then begin<br />

e := aristasgrafo[kmin];<br />

aristasgrafo[kmin] := aristasgrafo[hay]<br />

end;<br />

hay := hay - 1;<br />

(* miramos si los extremos estan<br />

en la misma componente *)<br />

with e do begin ri := find(i); rj := find(j) end;<br />

if (ri rj) then begin


Pág. 154 Grafos y árboles<br />

end;<br />

(* distintas componentes *)<br />

(* agregar arista a arbol *)<br />

marbol := marbol + 1;<br />

aristasarbol[marbol] := e;<br />

(* juntar componentes conexas *)<br />

union(ri, rj); nconexas := nconexas - 1<br />

end (* if ri rj *)<br />

end (* while nconexas y hay *)<br />

• Al finalizar, además de las aristas, podemos imprimir la cantidad de componentes<br />

conexas, nconexas, donde sabemos que |nconexas| =1⇔ el grafo<br />

es conexo y las aristas elegidas forman un árbol.<br />

Problema 13.11 (Algoritmo de Kruskal para el mínimo árbol generador).<br />

Hacer un programa con las indicaciones dadas, y verificarlo con las<br />

mismas entradas que en el problema 13.9 (figura 13.3). Es posible que la solución<br />

sea distinta. ✄<br />

✎ En nuestra implementación de Kruskal, la fuente de mayor ineficiencia<br />

está enlaselección de la arista de menor peso. En nuestra variante se tarda<br />

alrededor de m pasos en ubicarla, mientras que puede hacerse en el orden de<br />

log 2 m pasos. Tal cual está planteado, el algoritmo puede tardar del orden<br />

de m 2 pasos por el ordenamiento, pero en variantes más eficientes puede<br />

tardar del orden de m log 2 m pasos. Aún con nuestra implementación, el<br />

comportamiento para “grafos típicos” es mejor que el de Prim.<br />

13.7. Comentarios Bibliográficos<br />

La presentación unificada de los algoritmos visitar, Dijkstra y Prim está basada<br />

en [12].


Capítulo 14<br />

Punteros y listas<br />

encadenadas<br />

La lista encadenada surge de la necesidad de evitar la rigidez de la estructura<br />

de arreglo. Recordemos que los arreglos tienen una dimensión predefinida (en<br />

el programa fuente) que a veces resultará excesiva o escasa, dependiendo de la<br />

aplicación. También es muy difícil eliminar o agregar un elemento intermedio,<br />

pues debemos hacer un corrimiento de varios valores del arreglo (por ejemplo,<br />

en el método de clasificación por inserción directa).<br />

En contraste, veremos que la lista encadenada es una estructura dinámica,<br />

que se va construyendo y modificando de acuerdo a los datos que se ingresan.<br />

Para construir una lista encadenada, necesitamos la noción de puntero o<br />

apuntador, que introducimos a continuación.<br />

14.1. Punteros<br />

Recordemos que las variables que hemos usado hasta ahora ocupan un lugar<br />

de memoria, que será más grande o más chico dependiendo de su tipo, y que<br />

pensamos como “cajas”. Para reconocer una variable le hemos dado un nombre<br />

—su identificador— que tienen sentido para nosotros. Pero para la máquina, una<br />

vez compilado el programa, los nombres desaparecen y en cambio identifica cada<br />

variable por la dirección de memoria en la que empieza la caja y su contenido<br />

(y por lo tanto su longitud).<br />

Podemos pensar que un puntero es una variable donde nosotros guardaremos<br />

direcciones de memoria donde empiezan cajas. Aunque todas las direcciones<br />

(para una máquina y sistema operativo dados) ocupan el mismo espacio, en<br />

Pascal debemos agregar en la declaración de un puntero el tipo al cual “apunta”<br />

o “referencia” (esto es una propiedad del lenguaje, protegiéndonos de posibles<br />

errores).<br />

Por ejemplo, si queremos alojar en el puntero p una dirección de memoria<br />

en la que se guardará un entero, declaramos<br />

var p: ^integer<br />

✎ El símbolo ‘ ^ ’ es una abreviatura de ‘ ↑ ’, que normalmente no existe en<br />

los teclados.


Pág. 156 Punteros y listas encadenadas<br />

En este caso, al comienzo del programa p no tiene asignado valor alguno<br />

(como sucede con variables de otros tipos). Para indicar que p no tiene una caja<br />

relacionada, es muy conveniente y aconsejable (para evitar problemas) asignarle<br />

una dirección “imposible” que en Pascal se denomina nil:<br />

p := nil<br />

Podemos interpretar a nil como “un cable a tierra”, que indica que no hay<br />

“nada”. Recordemos que justamente hemos usado con el mismo sentido a la<br />

variable nada en el programa arbolbinario (pág. 197).<br />

A pesar de que, habiéndolo declarado, p ocupa un lugar de memoria, ese<br />

lugar es sólo para guardar direcciones, y puede no coincidir con la cantidad de<br />

espacio necesaria para guardar, por ejemplo, un entero. Si además queremos<br />

reservar un lugar para guardar un entero, usamos “ new ”:<br />

new(p)<br />

Esta instrucción hace que se reserve un lugar en memoria —la famosa “caja”—<br />

para alojar un dato de tipo entero (en este caso), y guarda en p la dirección<br />

de ese lugar, dejando a la caja disponible pero sin valor asignado.<br />

✎ Es posible que no haya más memoria para reservar, cosa improbable con<br />

las computadoras actuales y los programas que nosotros usaremos, por lo<br />

que no nos preocuparemos por esta eventualidad. En programación más<br />

“seria” también debemos preocuparnos por dejar libres los lugares obtenidos<br />

mediante “ new ”yqueyanoserán usados, que en Pascal se hace<br />

mediante “ dispose ”(quenoveremos).<br />

La variable (entera) a la que ahora “apunta” p se indica por pˆ. Así, si<br />

queremos poner el número3allí, hacemos<br />

p^ := 3;<br />

Es un error hacer una asignación a pˆ sin haber<br />

hecho antes una reserva de espacio con “ new ”.<br />

En la figura 14.1 esquematizamos la variable p yla“caja”pˆ alaque<br />

apunta. El valor alojado en p será asignado por la máquina al hacer “ new(p) ”<br />

y desconocido por nosotros, mientras que pˆ tiene alojado el valor 3 hecho<br />

por la asignación “ p^:= 3 ”. Los tamaños de las “cajas” p y pˆ notienenpor<br />

qué coincidir.<br />

p<br />

3<br />

pˆ<br />

Figura 14.1: El puntero p y la variable apuntada pˆ.


14.2. Listas encadenadas Pág. 157<br />

En (estándar) Pascal no es posible obtener la dirección de memoria de una<br />

variable. Por ejemplo si n es una variable entera, no podemos asignar a p la<br />

dirección de n. Por supuesto que no podemos hacer<br />

p := n (* incorrecto! *)<br />

pero si p y q son punteros del mismo tipo (declarado mediante “ type ”), es legal<br />

hacer<br />

q := p<br />

aún cuando p o q no hagan referencia a dato alguno, ya que estamos copiando<br />

en q la dirección guardada en p. Por otro lado, supuesto que p y q sean del<br />

mismo tipo y que ambos se hayan “inicializado” con new, podemos copiar los<br />

contenidos de una caja a la otra mediante<br />

q^ := p^<br />

Nuestro interés en este tipo de variables es en la creación de listas encadenadas.<br />

14.2. Listas encadenadas<br />

Una lista encadenada, o simplemente lista (1) , consiste en una serie de elementos,<br />

llamados nodos (2) unidos entre sí. Los nodos son registros (“ record ”’s),<br />

uno de cuyos campos es un puntero que señala al próximo elemento de la lista.<br />

Una declaración típica para los nodos es:<br />

type<br />

ptrnodo = ^nodo;<br />

nodo = record<br />

info: integer; (* puede haber mas de un<br />

campo de este estilo *)<br />

siguiente: ptrnodo<br />

end;<br />

Observar que la definición de los nodos es de alguna forma recursiva, ya que<br />

el tipo “ ptrnodo ”sólo puede definirse si está definido el tipo “ nodo ”, que a<br />

su vez sólo puede definirse si está definido el tipo “ ptrnodo ”. Sin embargo esta<br />

recursión es un tanto aparente pues las direcciones de memoria siempre ocupan<br />

el mismo lugar (que dependerá de la computadora y sistema operativo). Así, la<br />

reserva de espacio para “ ptrnodo ” es siempre la misma, independientemente<br />

del espacio que deba reservarse para una variable de tipo “ nodo ”.<br />

Las listas encadenadas tienen asociados un puntero donde se guarda la dirección<br />

donde comienza la lista. En nuestra implementación consideraremos que<br />

todos los nodos de la lista tienen información (3) , por lo que la lista “vacía” tiene<br />

el puntero asociado en nil (como nada en el programa arbolbinario). Del mismo<br />

modo, si la lista no es vacía, su último nodo apunta a nil como sucesor.<br />

(1) A veces, para confundir, también hablamos de listas cuando nos referimos a arreglos.<br />

(2) Como en los árboles binarios o grafos dirigidos. En realidad, las listas encadenadas se<br />

pueden pensar como grafos dirigidos.<br />

(3) Nuestra presentación difiere de otras donde siempre hay al menos un nodo.


Pág. 158 Punteros y listas encadenadas<br />

En forma similar a la descripción que hicimos de pilas o colas en general en<br />

la sección 12.1 (pág. 119), las operaciones fundamentales con listas son: inicialización,<br />

agregar nodos a la lista, sacar nodos y recorrer la lista.<br />

Suponiendo que lista es la variable declarada como “ ptrnodo ” que guarda<br />

el comienzo de la lista, tenemos:<br />

Inicialización de lista. Al comienzo la lista no tiene nodos, y ponemos<br />

lista := nil<br />

Agregar un nodo a la lista. Si queremos agregar el nodo p de tipo ptrnodo<br />

“adelante” en la lista, ponemos<br />

p^.siguiente := lista; lista := p<br />

pero puede ser que querramos construir la lista de otra forma, por ejemplo<br />

si queremos que esté ordenada.<br />

Si queremos agregar siempre atrás, es conveniente guardar también la dirección<br />

del último nodo de la lista, y debe preverse en la inicialización.<br />

Agregar un nodo antes o después de otro. Supongamos que p y q son del<br />

tipo “ ptrnodo ”, que pˆ esunelementodelalistayqˆ debe agregarse<br />

después de pˆ. El esquema simple es:<br />

q^.siguiente := p^.siguiente; p^.siguiente := q<br />

Si se desea insertar qˆ antes que pˆ, y no conocemos el antecesor de pˆ,<br />

podemos insertar qˆ después de pˆ y luego intercambiar las datos correspondientes:<br />

q^.siguiente := p^.siguiente; p^.siguiente := q;<br />

temp := p^.info; p^.info := q^.info; q^.info := temp<br />

Eliminar un elemento. Para sacar el nodo siguiente a pˆ, podemos hacer:<br />

q := p^.siguiente; p^.siguiente := q^.siguiente<br />

No podemos eliminar el nodo pˆ si no conocemos su antecesor (salvo que<br />

sea el primero de la lista). En este caso habrá que recorrer la lista hasta<br />

encontrar pˆ, guardando información sobre los antecesores.<br />

Recorrer la lista. Es bastante sencillo en nuestro caso (donde no hay nodos<br />

especiales para el principio o fin de lista):<br />

p := lista;<br />

while (p nil) do begin<br />

haceralgo; (* haceralgo con p o p^ *);<br />

p := p^.siguiente<br />

end


14.2. Listas encadenadas Pág. 159<br />

Problema 14.1. En el programa listas (pág. 206) se ven algunas posibilidades<br />

para construir listas, tanto agregando nodos adelante como en el medio para<br />

formar una lista ordenada por id, y el recorrido de una lista, para buscar una<br />

información o imprimir. Al final del listado del programa damos un ejemplo de<br />

salida (pág. 209).<br />

a) Estudiar cuidadosamente los distintos tipos de listas y la salida.<br />

b) Agregar un procedimiento o función para contar los elementos de una lista,<br />

imprimiendo luego el número. ✄<br />

Problema 14.2 (Listas recursivas). La definición de alguna forma recursiva<br />

del tipo “ ptrnodo ” nos hace pensar que podemos usar recursión para varias de<br />

las tareas.<br />

a) Por ejemplo, para imprimir una lista podríamos usar:<br />

procedure enorden (p: ptrnodo);<br />

begin<br />

if (p nil) then begin<br />

with p^.info do writeln(nombre:tamanio, id:10);<br />

enorden(p^.siguiente)<br />

end<br />

end;<br />

haciendo la llamada a “ enorden(lista)” (eventualmente imprimiendo un<br />

cartel antes si la lista es vacía). Agregar este procedimiento para comprobar<br />

su comportamiento.<br />

b) Cambiar el procedimiento enorden por enordeninverso, enelqueprimero<br />

se hace la llamada recursiva y luego se imprime info, y verificar su comportamiento.<br />

✄<br />

Problema 14.3. Hacer un procedimiento para construir una copia de una lista<br />

encadenada, es decir, una lista con tantos nodos como la original, y en la cual<br />

la información se guarda en el mismo orden que la lista original. ✄<br />

Problema 14.4. Hacer un procedimiento para “invertir” una lista de modo de<br />

que el nodo que estaba en primer lugar esté al final en la nueva lista y viceversa,<br />

etc., sin crear nuevos nodos (i.e. sin usar “ new ”).<br />

Sugerencia: ir formando una nueva lista en la que se incorpora el primer nodo<br />

de la vieja, borrándolo de la vieja, y continuar hasta terminar la lista original.<br />

Sugerencia si la anterior no alcanza: usar el esquema<br />

procedure darvuelta(var lista: ptrnodo);<br />

var p, q: ptrnodo;<br />

begin<br />

q := lista; lista := nil;<br />

while (q nil) do begin<br />

p := q; q := q^.siguiente;<br />

p^.siguiente := lista; lista := p<br />

end<br />

end;<br />

✎ El problema 8.3 hacía lo mismo para un vector, pero la técnica es muy<br />

diferente. En cambio, el esquema propuesto usa la idea del procedimiento<br />

“ enordeninverso ”delproblema14.2, agregando el nodo adelante en vez<br />

de imprimir. ✄


Pág. 160 Punteros y listas encadenadas<br />

Problema 14.5 (El problema de Flavio Josefo II). Implementar una solución<br />

a este problema (problema 6.7) usando en vez de arreglos una lista circular,<br />

i.e. el “último” nodo se encadena al “primero”, donde cada nodo representa una<br />

persona, y la eliminación se traduce en eliminar el nodo de la lista. El programa<br />

debe imprimir la permutación de Flavio Josefo.<br />

Sugerencia: crear una lista quedan de los que “quedan”, inicialmente con los valores<br />

1,...,n, donde el último nodo apunta al primero en vez de a nil. A medida<br />

que se eliminan nodos de quedan, ir formando la permutación de Flavio Josefo.<br />

En cada paso de “eliminación”, guardar información sobre el nodo anterior al<br />

eliminado, para eliminar el nodo y poder contar m de los que quedan a partir<br />

de él.<br />

✎ Recordar también el uso de “ mod ”enelproblemaoriginal. ✄<br />

Problema 14.6. Siguiendo las ideas del problema 14.4 y del procedimiento<br />

ordenada en el programa listas (pág. 206), hacer un procedimiento para ordenar<br />

(por alguna llave) una lista ya ingresada sin generar nuevos nodos. ✄<br />

Problema 14.7. Si una lista se modifica sólo agregando adelante o quitando<br />

el primer elemento, tenemos la estructura de “pila” que hemos usado para resolver<br />

el problema de las torres de Hanoi (problema 11.5). Hacer un programa<br />

resolviendo ese problema usando listas encadenadas en vez de arreglos. ✄<br />

14.3. Otras estructuras dinámicas<br />

En la construcción de listas, hemos colocado un puntero en los registros<br />

(“records”) para apuntar al siguiente elemento en la lista terminando con nil<br />

o volviendo a apuntar al principio de la lista para formar una lista “circular”.<br />

También podemos pensar en agregar en cada registro un puntero para señalar<br />

al elemento anterior (formando una lista doblemente encadenada), pero siempre<br />

podemos visualizar estas listas como “lineales” o “unidimensionales”.<br />

Podemos pensar, en cambio, en agregar punteros en los registros de modo<br />

de señalar a otras listas, que pueden tener el mismo tipo de registros o no.<br />

Si por ejemplo en el programa listas en vez de tener los nodos un campo<br />

para punteros, tenemos dos, y llamamos al primero “izquierdo” y al segundo<br />

“derecho”, obtenemos otra implementación de los árboles binarios ordenados<br />

que vimos en la sección 12.6, reemplazando la “pila” del arreglo por listas encadenadas.<br />

Problema 14.8. Declarando<br />

type<br />

ptrnodo = ^nodo;<br />

nodo = record<br />

llave: integer; (* numero leido *)<br />

cuenta: integer; (* veces que aparecio *)<br />

izquierda: ptrnodo; (* hijo a la izquierda *)<br />

derecha: ptrnodo (* hijo a la derecha *)<br />

end;<br />

rehacer el programa arbolbinario (pág. 197) reemplazando los arreglos por listas<br />

encadenadas, y luego realizar los incisos del problema 12.6. ✄


14.3. Otras estructuras dinámicas Pág. 161<br />

Recordemos que en el problema 8.6 estudiamos un arreglo bidimensional<br />

donde el primer índice indicaba el número de renglón, mientras que el segundo<br />

índice indicaba el número de carácter dentro de ese renglón. Al tener la estructura<br />

de arreglo una dimensión fija, estamos desperdiciando mucho espacio si<br />

queremos tener renglones de tamaños muy diferentes, o si no tenemos mucha<br />

idea de cuántos renglones habrá en el texto completo. Es mucho más apropiado<br />

tomar una lista encadenada, representando cada renglón, y donde sus nodos<br />

apuntan no sólo al siguiente renglón sino también a una lista con el texto de ese<br />

renglón. Los editores de textos trabajan con una estructura similar (escribiendo<br />

luego secuencialmente en el disco cuando guardamos el archivo).<br />

Un problema matemático donde se puede usar una idea similar es en grafos,<br />

al tener la descripción por lista de aristas o por vértices adyacentes a otro dado.<br />

Problema 14.9. Como hemos visto en el capítulo anterior, un grafo G consiste<br />

de un conjunto de vértices V = {1, 2,...,n}, y un conjunto de aristas E, donde<br />

cada arista es de la forma {u, v} con u, v ∈ V , u = v (y supondremos que no<br />

hay aristas repetidas).<br />

Es usual representar los vértices mediante puntos y las aristas con segmentos<br />

o curvas que unen los vértices, como se muestra en la figura 13.1 (pág. 134), en<br />

donde n =6yE = {{1, 2}, {1, 3}, {2, 3}, {2, 6}, {3, 4}, {3, 6}, {4, 6}}.<br />

Por otra parte, en vez de describir G mediante E (y V ), podríamos hacerlo<br />

a partir de las listas de adyacencias o vecinos: para cada u ∈ V el conjunto de<br />

vértices {v ∈ V : {u, v} ∈E}. En el ejemplo anterior, podríamos poner:<br />

1 →{2, 3}, 2 →{1, 3, 6}, 3 →{1, 2, 4, 6}, 4 →{3, 6}, 5 →∅, 6 →{2, 3, 4}.<br />

Para representar el grafo en la computadora usando listas encadenadas (4) ,<br />

podemos recurrir a las estructuras:<br />

Para lista de aristas: Una lista de aristas, donde cada arista se representa<br />

mediante<br />

ptrarista = ^arista;<br />

arista = record u, v: integer; s: ptrarista end;<br />

Para lista de adyacencias: Una lista de vértices, donde cada vértice<br />

tiene un puntero a una lista de vecinos:<br />

ptrvecino = ^vecino;<br />

vecino = record id: integer; s: ptrvecino end;<br />

ptrvertice = ^vertice;<br />

vertice = record<br />

id: integer;<br />

s: ptrvertice;<br />

vecinos: ptrvecino<br />

end;<br />

El programa grafos (pág. 209) imprime la listas de adyacencias si el grafo<br />

es ingresado dando n y E. Estudiarlo y modificarlo de modo de realizar la<br />

operación inversa, i.e. imprimir los aristas cuando el grafo se ingresa dando n y<br />

las listas de adyacencias. ✄<br />

(4) En el capítulo 13 hemos considerado algunas representaciones con arreglos y matrices.


Pág. 162 Punteros y listas encadenadas<br />

14.4. Problemas Adicionales<br />

Problema 14.10 (Ciclo de Euler). En el problema 13.7 nos hemos referido<br />

a la existencia de ciclos de Euler, y nos preocuparemos ahora por encontrar<br />

efectivamente uno.<br />

Para demostrar que un grafo conexo con todos los vértices de grado par tiene<br />

unciclodeEuler,secomienzaapartirdeunvértice y se van recorriendo aristas<br />

y borrándolas hasta que volvamos al vértice original, formando un ciclo. Esto<br />

debe suceder porque todos los vértices tienen grado par. Puede ser que el ciclo<br />

no cubra a todas las aristas, pero como el grafo es conexo, debe haber un vértice<br />

en el ciclo construido que tenga una arista (aún no eliminada) incidente en él, a<br />

partir del cual podemos formar un nuevo ciclo, agregarlo al anterior y continuar<br />

con el procedimiento hasta haber eliminado recorrido todas las aristas.<br />

Esta demostración es bien constructiva, y podemos implementar los ciclos<br />

como listas encadenadas. La implementación de aristas, etc. puede hacerse con<br />

arreglos o matrices.<br />

Hacer un programa para decidir si un grafo ingresado como en el programa<br />

anchoprimero (pág. 199) es conexo y todos los vértices tienen grado par, y<br />

en caso afirmativo construir e imprimir un ciclo de Euler. ✄<br />

14.5. Comentarios Bibliográficos<br />

La presentación de punteros y listas encadenadas está basada en [16], tomando<br />

también ideas de [10].


Capítulo 15<br />

Más problemas<br />

En este capítulo agregamos más problemas para los entusiastas o los que simplemente<br />

quieran practicar más (¡por ejemplo para algún examen!), no estando<br />

ordenados ni por tema ni por dificultad.<br />

Problema 15.1 (Polinomios interpoladores de Lagrange). Un polinomio<br />

P (x) =anx n + an−1x n−1 + ···+ a1x + a0, de grado a lo sumo n, está determinado<br />

por los n + 1 coeficientes. Supongamos que no conocemos los coeficientes,<br />

pero podemos conocer los valores de P (x) en algunos puntos, ¿cuántos puntos<br />

necesitaremos para determinar los coeficientes? Como hay n + 1 coeficientes,<br />

es natural pensar que quedarán determinados por n + 1 ecuaciones, i.e. que<br />

bastarán n + 1 puntos.<br />

Este resultado es cierto, y se puede demostrar planteando un sistema de n+1<br />

ecuaciones lineales, y viendo que su determinante —de Van der Monde— que<br />

se estudia en Álgebra Lineal es no nulo.<br />

Dados (xi,yi), i =1,...,n+1, elpolinomio interpolador de Lagrange se<br />

define como<br />

n+1 <br />

P (x) =<br />

<br />

yi<br />

xi − xj<br />

i=1 j=i<br />

x − xj<br />

. (15.1)<br />

✎ El polinomio en general resulta de grado ≤ n, y no necesariamente n.<br />

Pensar, por ejemplo, en 3 puntos sobre una recta: determinan un polinomio<br />

de grado 1 y no 2.<br />

✎ En cálculo numérico se ven formas más eficientes (esencialmente extensiones<br />

de la regla de Horner) para el cómputo de estos polinomios interpoladores.<br />

1<br />

0.5<br />

π<br />

6<br />

π<br />

4<br />

π<br />

2<br />

Figura 15.1: Aproximación de sen x (en trazo discontinuo) mediante un polinomio<br />

de grado 3.<br />

π


Pág. 164 Más problemas<br />

a) Ver que efectivamente, P (x) definido por la ecuación (15.1)satisfaceP (xi) =<br />

yi para i =1,...,n+1.<br />

b) Desarrollar un procedimiento para evaluar P (x) dado por la ecuación (15.1),<br />

donde los datos son (xi,yi), 1 ≤ i ≤ n +1, y x. Aclaración: sólose pide una<br />

traducción “literal” de la ecuación (15.1).<br />

c) Utilizarlo en un programa que calcule los coeficientes del polinomio de grado<br />

a lo sumo 3 que pasa por los puntos (−1, 0), (0, 1), (1, 0), (2, 2),enelpunto<br />

x = −2.<br />

d) Ampliar el programa para calcular una aproximación de sen π/4, usando los<br />

valores del seno para 0,π/6,π/2yπ.<br />

✎ Es usual poner π =4×arctan 1, aunque en este problema en particular<br />

no se necesita un valor específico de π (considerar la función sen πx en<br />

vez de sen x).<br />

La figura 15.1 muestra cómo se parecen las gráficas de sen x y el polinomio<br />

en el intervalo [0,π].<br />

Aunque considerado como francés, Joseph-Louis Lagrange (1736–1813) nació<br />

enTurín (Italia) como Giuseppe Lodovico Lagrangia.<br />

Lagrange fue uno de los fundadores del “cálculo de variaciones” (área relacionada<br />

con la mecánica) y las probabilidades, e hizo numerosas contribuciones<br />

en otras áreas como astronomía y ecuaciones diferenciales. ✄<br />

Problema 15.2 (Cuenta de Goldbach). En el problema 5.23 hablamos de<br />

la conjetura de Golbach, que todo número par mayor o igual a 6 puede escribirse<br />

como suma de dos primos impares.<br />

La cuenta de Goldbach es la cantidad de formas en las que un número par<br />

puede escribirse como suma de dos primos impares. Por ejemplo, 20 = 13 + 7 =<br />

17 + 3, por lo que la cuenta de Goldbach de 20 es 2.<br />

Hacer un programa para determinar la cuenta de Goldbach de todos los<br />

pares entre 2 y 10000, y encontrar el máximo. ✄<br />

Problema 15.3. Hacer un programa para simular el juego de la generala en el<br />

que se tiran 5 dados (una única vez), y se tiene “full” cuando hay 3 dados con<br />

un mismo número y los otros 2 con otro número, “póker” cuando hay 4 dados<br />

iguales, “escalera” cuando hay 5 números consecutivos, y “generala” cuando los<br />

5 dados son iguales. Sugerencia: una vez “tirados” los dados, clasificarlos para<br />

determinar si se ha obtenido alguno de los juegos. ✄<br />

Problema 15.4 (Visibilidad en el plano). Consideremos el reticulado C<br />

definido por los puntos del plano con ambas coordenadas enteras. Diremos que<br />

el punto P ∈Ces visible (desde el origen O) si el segmento que une P y O no<br />

contiene otros puntos de C distintos de P y O.<br />

a) Si a, b ∈ N, entonces (a, b) es visible ⇔ mcd(a, b) =1.<br />

b) Desarrollar un programa que dado n ∈ N calcule sn, la cantidad de puntos<br />

visibles en el cuadrado 1 ≤ a, b ≤ n.<br />

c) Se puede demostrar que a medida que n crece, pn = sn/n2 se aproxima<br />

a p =6/π2 =0.6079 ....Calcularpnpara distintos valores de n para ver<br />

que este es el caso. Sugerencia: empezar con n pequeño e ir aumentando<br />

paulatinamente su valor. ✄


Problema 15.5 (Aproximando e con dados). Hacer un programa para<br />

simular una máquina que emite números al azar (uniformemente distribuidos)<br />

en el intervalo (0, 1) uno tras otro hasta que su suma excede 1. Comprobar que<br />

al usar la máquina muchas veces, la cantidad promedio de números emitidos es<br />

aproximadamente e =2.71828 ....<br />

✎<br />

Éste es otro ejemplo del método de Monte-Carlo, esta vez para aproximar<br />

e. ✄<br />

Problema 15.6 (Generando “manos” aleatoriamente). Queremos resolver<br />

el problema de encontrar una “mano” de los números enteros entre 1 y n, es<br />

decir, queremos “mezclar” un mazo de n cartas de un mismo palo (matemáticamente,<br />

obtener una permutación aleatoria de los números entre 1 y n).<br />

Presentamos varias posibilidades:<br />

a) Imaginando que empezamos con la cartas numeradas de 1 a n, ordenadas<br />

crecientemente, hacemos el siguiente procedimiento: sacamos una carta al<br />

azar entre 1 y n y la separamos, de las restantes sacamos una al azar (entre<br />

1yn − 1) y la separamos, y así sucesivamente. Sin embargo, computacionalmente<br />

esta tarea no es sencilla, debido al manejo que debemos hacer de<br />

listas.<br />

b) Una variante es: al elegir la primera carta la colocamos adelante y ponemos<br />

la primera carta en el lugar de la elegida. Luego elegimos al azar alguna de<br />

las cartas que están en las posiciones entre 2 y n y la intercambiamos con la<br />

segunda, y así sucesivamente. Implementar esta idea computacionalmente.<br />

c) En el algoritmo anterior, podríamos reemplazar el intercambiar la carta<br />

elegida con la primera, después con la segunda, etc., por intercambiar con<br />

la última, después con la ante-última, etc. Implementar también esta idea<br />

computacionalmente.<br />

d) Usando alguno de los algoritmos anteriores, hacer un programa para simular<br />

una “mano” de m cartas de un total de n. Por ejemplo, para un jugador en<br />

el truco elegiríamos m =3yn = 40.<br />

✎ ¡Noesnecesariogenerartodalapermutación!<br />

Aunque el problema de las “manos” es muy sencillo de plantear, y se usa<br />

con mucha frecuencia, encontrar algoritmos correctos y eficientes no es sencillo.<br />

La corrección de un algoritmo se refiere a que 1) todas las permutaciones<br />

de 1,...,n se pueden generar de esta forma, y 2) con igual probabilidad. Otro<br />

problema es el de decidir su eficiencia: cuánto tiempo e intercambios realiza en<br />

términos de n. Ninguna de estas preguntas es fácil de responder.<br />

✎ Observar la similitud entre la clasificación por selección directa o inserción<br />

directa con la obtención de “manos” aleatorias en los incisos b) ya)<br />

respectivamente.<br />

Estas similitudes son la base para el estudio teórico de los métodos de<br />

clasificación: peor caso, caso promedio, etc.<br />

✎ El algoritmo en b) estápresentadoen[11, Vol.2,pág. 126], donde se hacen<br />

los análisis de corrección y eficiencia. Es atribuído independientemente a<br />

L. E. Moses y R. V. Oakford (1963) y R. Durstenfeld (1964).<br />

✎ Existen muchos otros algoritmos relacionados con este problema. Los interesados<br />

pueden consultar el libro de Knuth ya mencionado. Otro algoritmo<br />

interesante, debido a R. Floyd, está descripto en [2]. ✄<br />

Pág. 165


Pág. 166 Más problemas<br />

Problema 15.7 (Clasificación por fusión). Los métodos elementales usan<br />

del orden de n 2 comparaciones y/o asignaciones para clasificar un arreglo de<br />

longitud n. En contraposición, los métodos más avanzados usan del orden de<br />

n × log 2 n operaciones. Entre los más “populares” de este tipo podemos mencionar<br />

al heapsort o clasificación por montón, quicksort o clasificación rápida, y<br />

clasificación por fusión o mezcla (ya mencionado en la sección 10.3), que veremos<br />

ahora.<br />

No es difícil explicar este último método, cuya idea es dividir el arreglo<br />

de a pares, y luego ir fusionando —como en el problema 10.4— los subarreglos:<br />

primero juntar pares consecutivos, luego cuaternas consecutivas, etc. hasta llegar<br />

obtener el arreglo completo. Por ejemplo, el método realiza los siguientes pasos<br />

al clasificar (3, 6, 7, 5, 4, 1, 2):<br />

1. 3 6 75412 se juntan de a pares (o lo que se pueda),<br />

2. 3 6 57142 se ordena cada par (i.e. se “fusiona” cada grupo),<br />

3. 3 6 57142 se agrupan de a cuatro (o lo que se pueda),<br />

4. 3567124 se “fusiona” cada grupo,<br />

5. 3567124 se agrupan de a ocho (o lo que se pueda),<br />

6. 1234567 y se fusionan.<br />

A fin de implementar este algoritmo, usamos el procedimiento clasificar que<br />

llama, a su vez, al fusionar:<br />

procedure fusionar(<br />

var a, b: arreglo; (* entra a, sale b *)<br />

inic, medio, fin: integer (* partes a fusionar *)<br />

);<br />

var<br />

i, j, k: integer;<br />

begin<br />

i := inic; j := medio + 1; k := inic - 1;<br />

repeat<br />

k := k + 1;<br />

if (a[i] medio) or (j > fin));<br />

(* copiar lo que falta *)<br />

while (j


a2: arreglo;<br />

begin<br />

k := 1; (* tamanio de subarreglos a fusionar *)<br />

veces := 0; (* las veces que paso por el lazo *)<br />

while (k < n) do begin<br />

veces := veces + 1; k2 := 2 * k;<br />

i := 1; m := k; f := k2;<br />

if ((veces mod 2) = 0) then begin<br />

while (f


Pág. 168 Más problemas<br />

ficar que su comportamiento es correcto, y cotejar el número de asignaciones<br />

(entre arreglos) y comparaciones con los otros algoritmos elementales a fin<br />

de obtener un cuadro similar al 10.1 (sin los tiempos, y para arreglos más<br />

chicos). ✄<br />

Problema 15.8. Al estudiar los datos numéricos (a1,a2,...,an) estadísticamente,<br />

se consideran (entre otros) tres tipos de “medidas”:<br />

La media o promedio: 1<br />

n<br />

ai.<br />

n<br />

i=1<br />

La mediana: Intuitivamente es un valor aℓ tal que la mitad de los datos<br />

son menores o iguales que aℓ y la otra mitad son mayores o iguales que<br />

aℓ. Para obtenerla, se ordenan los datos y la mediana es el elemento<br />

del medio si n es impar y es el promedio de los dos datos en el medio<br />

si hay un número par de datos.<br />

✎ En realidad no es necesario ordenar todos los datos para obtener<br />

la mediana. Por otra parte, observar que la mediana puede no<br />

serundatoenelcasoden par.<br />

La moda: Es un valor aℓ con frecuencia máxima, y puede haber más de<br />

una moda.<br />

Por ejemplo, si los datos son 8, 0, 4, 6, 7, 8, 3, 2, 7, 4, la media es 4.9, la mediana<br />

es 5, y las modas son 4, 7 y 8.<br />

Hacer un programa para generar aleatoriamente un arreglo de longitud n<br />

de números entre 0 y 9, y luego encontrar la media, mediana y moda/s (n es<br />

ingresado por el usuario). ✄<br />

Problema 15.9 (Números de Fibonacci: Zeckendof (1972)). Todo número<br />

entero se puede escribir, de forma única, como suma de (uno o más) números<br />

de Fibonacci no consecutivos, es decir para todo n ∈ N existe una única representación<br />

de la forma<br />

n = b2f2 + b3f3 + ···+ bmfm,<br />

donde bm =1,bk ∈{0, 1}, ybk · bk+1 =0parak =2,...,m− 1.<br />

Por ejemplo, 10 = 8 + 2 = f6 + f3,27=21+5+1=f8 + f5 + f1.<br />

Hacer un programa para calcular esa representación (dando por ejemplo una<br />

lista de los índices k para los que bk =1en(15.9)). Comprobar con algunos<br />

ejemplos la veracidad de la salida. ✄<br />

Problema 15.10 (La Cola del Supermercado). Hacer un programa que<br />

simule una “cola”, donde el tiempo entre dos llegadas consecutivas de clientes<br />

puede considerarse como un número entero (aleatorio y uniformemente distribuido)<br />

de entre 1 y 5 minutos. Suponer que la atención de cada cliente dura<br />

un tiempo fijo de 2 minutos. Hacer la simulación por un período de 8 horas,<br />

computando el porcentaje de uso del servicio, el tiempo que no se ha usado,<br />

la longitud promedio de la cola, el tiempo de espera promedio de los clientes<br />

ycuántos clientes no fueron atendidos al final del período (se supone que los<br />

clientes pueden comenzar a ser atendidos hasta el último instante). Variar los<br />

datos de frecuencia de clientes y tiempo de atención para ver cómo varía el<br />

comportamiento de la “cola”, por ejemplo incrementar el tiempo de atención de<br />

2 a 5 minutos. Discutir estos resultados, experimentando: tiempo de atención


para que los clientes no esperen más de 3 minutos (en promedio), o para que la<br />

cola no crezca indefinidamente, etc. ✄<br />

Problema 15.11 (La Producción de la Fábrica). Supongamos que una<br />

pequeña empresa fabrica un cierto tipo de producto. La empresa quiere contratar<br />

obreros, cada uno de los cuales, con las herramientas disponibles, podrá fabricar<br />

28 unidades diarias del producto. Nuestro objetivo es, mediante simulación,<br />

determinar la cantidad de obreros necesarios para que el atraso en la producción<br />

sea razonable, sin tener mano de obra ociosa excesiva. (Suponemos que no se<br />

mantiene stock).<br />

De experiencia anterior se sabe que los pedidos diarios del producto (la<br />

demanda) sigue en promedio una distribución como se indica:<br />

promedio pedido (unidades) 100 200 300 400 500 600<br />

frecuencia 0.15 0.25 0.18 0.22 0.13 0.07<br />

a) Hacer un programa para analizar la producción diaria durante un cierto<br />

período, digamos durante 30 ó60días, para ver qué cantidad de obreros<br />

será necesaria.<br />

b) Comprobar que los pedidos siguen (aproximadamente) la ley dada por las<br />

frecuencias. (O sea que hay pedidos de 100 el 15 % de las veces, de 200 el<br />

40 %, etc.). Tal vez sea conveniente aumentar la longitud del período de<br />

simulación. ✄<br />

Problema 15.12 (Pasos en búsqueda binaria). La función f que estudiamos<br />

en este problema está relacionada con la cantidad de pasos que se realizan<br />

en la búsqueda binaria cuando hay n elementos.<br />

Consideremos f : N → N definida por f(n) =⌊(n +1)/2⌋, yparak ∈ N,<br />

F (k, n) =f k (n) =f(···(f(n))<br />

···).<br />

<br />

k veces<br />

a) Hacer un programa para calcular F (k, n) usando un lazo “ for ”, recordando<br />

que para n ∈ N, ⌊(n +1)/2⌋puede escribirse en Pascal como (n + 1) div<br />

2.<br />

b) ¿Cuál es el valor de F (k, 2k )? (probar con el programa para algunos valores<br />

de k, hacer una conjetura y tratar de demostrarla).<br />

c) Para cada n>2, existe j = g(n) talqueF (k, n) > 1sik


Pág. 170 Más problemas<br />

A, toma cualquier número de fósforos de un fila, pudiendo tomar<br />

uno, dos o hasta toda la fila, pero sólo debe modificar una fila. El<br />

jugador B juega de manera similar con los fósforos que quedan, y<br />

los jugadores van alternándose en sus jugadas. Gana el jugador que<br />

saca el último fósforo.<br />

Veremos que, dependiendo de la posición inicial, uno u otro jugador tiene<br />

siempre una estrategia ganadora. Para esto, digamos que una disposición de los<br />

fósforos es una posición ganadora para el jugador X, sidejando X los fósforos<br />

en esa posición, entonces no importa cual sea la jugada del oponente, X puede<br />

jugar de forma tal de ganar el juego. Por ejemplo, la posición en la cual hay dos<br />

filas con dos fósforos cada una, es ganadora: si A deja esta posición a B, B debe<br />

tomarunoodosfósforos de una fila. Si B toma 2, A toma los dos restantes. Si<br />

B toma uno, A toma uno de la otra fila. En cualquier caso, A gana.<br />

a) Ver que la posición en donde hay 3 filas con 1, 2 y 3 fósforos respectivamente,<br />

es ganadora.<br />

Para encontrar una estrategia ganadora, formamos una tabla expresando el<br />

número de fósforos de cada fila en binario, uno bajo el otro, poniendo en una<br />

fila final “ P ” si la suma total de la columna correspondiente es par, e “ I ”en<br />

otro caso. Por ejemplo, en las posiciones anteriores haríamos:<br />

1 0<br />

1 0<br />

P P<br />

y<br />

0 1<br />

1 0<br />

1 1<br />

P P<br />

En el último ejemplo, es conveniente poner 01 en vez de sólo 1 en la primer<br />

fila a fin de tener todas las filas con igual longitud.<br />

Si la suma de cada columna es par decimos que la posición es correcta, y<br />

en cualquier otro caso decimos que la posición es incorrecta. Por ejemplo, la<br />

posición donde hay 1, 3 y 4 fósforos es incorrecta:<br />

| = 0 0 1<br />

| | | = 0 1 1<br />

| | | | = 1 0 0<br />

I I P<br />

b) En este inciso, veremos que una posición en Nim es ganadora si y sólo si es<br />

correcta.<br />

i) Si ninguna fila tiene más de un fósforo, la posición es ganadora ⇔ hay<br />

en total un número par de fósforos ⇔ la posición es correcta.<br />

ii) Si un número a ∈ N, expresado en binario por (an,an−1,...,a1,a0)<br />

(permitiendo an = 0) es reemplado por otro menor b (entero ≥ 0),<br />

expresado en binario como (bn,bn−1,...,b1,b0), entonces para algún<br />

i, 0≤ i ≤ n, las paridades de ai y bi son distintas.<br />

iii) Por lo tanto, si el jugador X recibe una posición correcta, necesariamente<br />

la transforma en incorrecta.<br />

iv) Supongamos que estamos en una posición incorrecta, es decir, al menos<br />

la suma de una columna es impar. Para fijar ideas supongamos que las<br />

paridades de las columnas son


P P I P I P<br />

Entonces hay al menos un 1 en la tercera columna (la primera con<br />

suma impar). Supongamos, otra vez para fijar ideas, que una fila en la<br />

cual esto pasa es (en binario)<br />

- -<br />

0 1 1 1 0 1<br />

donde marcamos con “ - ”quelosnúmeros debajo están en columnas<br />

de suma impar. Cambiando 0’s y 1’s por 1’s y 0’s en las posiciones<br />

marcadas, obtenemos el número (menor que el original, expresado en<br />

binario):<br />

- -<br />

0 1 0 1 1 1<br />

Estáclaroqueestecambiocorrespondeaunmovimientopermitido,<br />

haciendo la suma de cada columna par, y que el argumento es general.<br />

v) Por lo tanto, si el jugador X recibe una posición incorrecta, puede<br />

moverdemododedejarunaposición correcta.<br />

vi) Si A deja una posición correcta, B necesariamente la convierte en<br />

incorrecta y A puede jugar dejando una posición correcta. Este proceso<br />

continuará hasta que cada fila quede vacía o contenga un único fósforo<br />

(el caso i)).<br />

c) En base a los incisos anteriores, y suponiendo que A y B siempre juegan de<br />

modo óptimo, ¿quién ganará?<br />

d) Ver que la “jugada ganadora” del inciso b.iv) nosiempreesúnica.<br />

e) Desarrollar un programa que, ingresada una posición inicial en la forma de<br />

una lista con el número de fósforos en cada fila, decida si la posición es<br />

correcta o incorrecta, y en este caso encuentre una jugada ganadora.<br />

f )¿cuál es la “estrategia” si el que saca el último fósforo es el que pierde? ✄<br />

Comentarios Bibliográficos<br />

El problema 15.4 está tomado de [4]. Para el problema 15.6, ver las citas<br />

allí mencionadas. El problema 15.10 está tomado de [14], y el problema 15.11<br />

de [1]. El juego de Nim (problema 15.13) y su solución está tomado de [6].<br />

Pág. 171


Apéndice A<br />

Programas mencionados<br />

Problema 2.2: holamundo (pág. 10)<br />

program holamundo(input, output);<br />

(* primer programa *)<br />

begin<br />

writeln(’Hola Mundo!’);<br />

writeln;<br />

writeln(’y Chau!’)<br />

end.<br />

Problema 3.2: sumardos (pág. 16)<br />

program sumardos(input, output);<br />

(* sumar dos numeros enteros *)<br />

var a, b: integer;<br />

begin<br />

writeln(’** Programa para sumar dos numeros enteros’);<br />

writeln;<br />

a := 1;<br />

b := 2;<br />

write(’La suma de ’, a);<br />

write(’ y ’, b);<br />

writeln(’ es ’, a + b);<br />

writeln; writeln(’** Fin **’)<br />

end.<br />

Problema 3.3: leerdato (pág. 17)<br />

program leerdato(input, output);<br />

(* Leer un entero entrado por terminal *)<br />

var a: integer;


Pág. 174 Programas mencionados<br />

begin<br />

writeln(’** Programa para leer un dato entero’);<br />

writeln;<br />

write(’Entrar un entero: ’); readln(a);<br />

writeln;<br />

writeln(’El entero leido es ’, a);<br />

writeln; writeln(’** Fin **’)<br />

end.<br />

Problema 3.4: raiz (pág. 18)<br />

program raiz(input, output);<br />

(*<br />

Obtener la raiz cuadrada de un numero real x.<br />

Se supone que x no es negativo.<br />

*)<br />

var x, y: real;<br />

begin<br />

writeln(’** Calcular la raiz cuadrada de x **’);<br />

writeln;<br />

write(’Entrar x (> 0): ’); readln(x);<br />

y := sqrt(x);<br />

writeln;<br />

writeln(’La raiz cuadrada de ’, x, ’ es ’, y);<br />

writeln; writeln(’** Fin **’)<br />

end.<br />

Problema 3.7: enteroareal (pág. 20)<br />

program enteroareal(input, output);<br />

(* Asignar un entero a un real *)<br />

var<br />

a: integer;<br />

x: real;<br />

begin<br />

writeln(’** Asignar un entero a un real’);<br />

writeln;<br />

write(’Entrar el valor del entero: ’); readln(a);<br />

x := a;<br />

writeln;<br />

writeln( a, ’ cambiado a real es: ’, x);<br />

writeln; writeln(’** Fin **’)<br />

end.


segundos (Problema 3.13) Pág. 175<br />

Problema 3.13: segundos (pág. 22)<br />

program segundos(input, output);<br />

(* Pasar de segundos a horas, minutos y segundos *)<br />

var hs, mins, segs: integer;<br />

begin<br />

write(’** Programa para pasar de segundos’);<br />

writeln(’ a horas, minutos y segundos’);<br />

writeln;<br />

write(’Entrar la cantidad de segundos: ’); readln(segs);<br />

writeln;<br />

writeln(segs, ’ segundos son equivalentes a ’);<br />

mins := segs div 60; segs := segs mod 60;<br />

hs := mins div 60; mins := mins mod 60;<br />

writeln(hs, ’ hs, ’, mins, ’ mins, ’, segs, ’ segs.’);<br />

writeln; writeln(’** Fin **’)<br />

end.<br />

Problema 3.16: positivo (pág. 22)<br />

program positivo(input, output);<br />

(* dice si un numero entero es positivo *)<br />

var a: integer;<br />

begin<br />

writeln(’** Ver si un numero entero es positivo’);<br />

writeln;<br />

write(’Entrar un numero: ’); readln(a);<br />

writeln;<br />

writeln(’es positivo?: ’, a > 0);<br />

writeln; writeln(’** Fin **’)<br />

end.<br />

Problema 3.18: caracteres1 (pág. 24)<br />

program caracteres1(input, output);<br />

(* pasar de caracter a numero y viceversa *)<br />

var c: char; i: integer;<br />

begin<br />

writeln(’** Pasar de caracter a numero y viceversa’);<br />

writeln;<br />

write(’ Entrar un caracter: ’); readln(c);<br />

writeln;<br />

i := ord(c);<br />

writeln(’ El numero de orden de ’, c, ’ es ’, i:1);


Pág. 176 Programas mencionados<br />

(* i:1 indica que escribira i en un solo espacio,<br />

o los que sean necesarios *)<br />

write(’ Verificacion:’);<br />

write(’ el caracter correspondiente a ’, i:1);<br />

writeln(’ es ’, chr(i));<br />

writeln; writeln(’** Fin **’)<br />

end.<br />

Problema 4.2: valorabsoluto (pág. 26)<br />

program valorabsoluto(input, output);<br />

(* encuentra el valor absoluto del numero real x *)<br />

var x, y: real;<br />

begin<br />

writeln(’** Encontrar el valor absoluto de un numero real’);<br />

writeln;<br />

write(’Entrar el numero: ’); readln(x);<br />

if (x >= 0) then y := x else y := -x;<br />

writeln;<br />

writeln(’El valor absoluto de ’, x, ’ es ’, y);<br />

writeln; writeln(’** Fin **’)<br />

end.<br />

Problema 4.4: comparar (pág. 27)<br />

program comparar(input, output);<br />

(* decidir cual numero entero es mayor o si son iguales *)<br />

var a, b: integer;<br />

begin<br />

writeln(’** Comparar dos numeros enteros’);<br />

writeln;<br />

write(’Entrar un numero entero: ’); readln(a);<br />

write(’Entrar otro numero entero: ’); readln(b);<br />

writeln;<br />

if (a > b) then<br />

writeln(a, ’ es mayor que ’, b)<br />

else if (a < b) then<br />

writeln(b, ’ es mayor que ’, a)<br />

else<br />

writeln(a, ’ es igual a ’, b);<br />

writeln; writeln(’** Fin **’)<br />

end.<br />

Problema 4.5: caracteres2 (pág. 27)<br />

program caracteres2(input, output);


esto (Problema 4.11) Pág. 177<br />

(* decidir si un caracter es una letra mayuscula o<br />

minuscula, o no es letra, comparando caracteres *)<br />

var c: char;<br />

begin<br />

writeln(’** Decide si un caracter es una letra ’);<br />

writeln(’ mayuscula o minuscula o no es letra’);<br />

writeln;<br />

write(’ Entrar un caracter: ’); readln(c); writeln;<br />

write(c);<br />

if (’a’


Pág. 178 Programas mencionados<br />

(* hacer una tabla del seno donde los angulos<br />

estan dados en grados *)<br />

const pi = 3.14159265;<br />

var<br />

inicial, final, incremento, grados: integer;<br />

radianes: real;<br />

begin<br />

writeln(’** Hacer una tabla del seno dando valores’);<br />

writeln(’ inicial, final, y del incremento (en grados).’);<br />

writeln;<br />

write(’Entrar el valor inicial (en grados): ’);<br />

readln(inicial);<br />

write(’Entrar el valor final (en grados): ’);<br />

readln(final);<br />

write(’Entrar el valor de incremento (en grados): ’);<br />

readln(incremento);<br />

writeln;<br />

writeln(’Angulo Seno’);<br />

grados := inicial;<br />

while (grados


cifras (Problema 4.16) Pág. 179<br />

while (n > 0) do begin suma := suma + n; n := n-1 end;<br />

writeln;<br />

writeln(’ suma con while al reves: ’, suma:10:0);<br />

(* 10:0 indica que el numero se escribe en<br />

al menos 10 espacios, sin decimales *)<br />

writeln; writeln(’** Fin **’)<br />

end.<br />

Problema 4.16: cifras (pág. 34)<br />

program cifras(input, output);<br />

(* determinar la cantidad de cifras significativas<br />

(en base 10, ignorando el signo) de un entero<br />

entrado por terminal.<br />

E.g. 246 -> 3, -908 -> 3, pero 0 -> 1 *)<br />

var a, b, c: integer;<br />

begin<br />

writeln(’** Determinar la cantidad de cifras de un’);<br />

writeln(’ entero (sin tener en cuenta el signo)’);<br />

writeln;<br />

write(’Entrar el numero: ’); readln(a); writeln;<br />

if (a < 0) then b := -a else b := a;<br />

c := 0;<br />

repeat<br />

c := c + 1;<br />

b := b div 10<br />

until b = 0;<br />

writeln(’La cantidad de cifras de ’, a:1, ’ es ’, c:1);<br />

writeln; writeln(’** Fin **’)<br />

end.<br />

Problema 4.17: epsmin (pág. 34)<br />

program epsmin(input, output);<br />

(*<br />

epsmin = menor numero real mayor que 0.<br />

epsmaq = menor numero real que sumado a 1 da mayor que 1.<br />

*)<br />

var eps, x: real;


Pág. 180 Programas mencionados<br />

begin<br />

writeln(’** Determinacion de epsmin y epsmaq’); writeln;<br />

x := 1;<br />

repeat eps := x; x := x / 2 until (x = 0);<br />

writeln(’epsmin es: ’, eps);<br />

eps := 1;<br />

while (1 + eps > 1) do eps := eps/2;<br />

eps := 2 * eps;<br />

writeln(’epsmaq es: ’, eps);<br />

writeln; writeln(’** Fin **’)<br />

end.<br />

Problema 4.18: potencia (pág. 36)<br />

program potencia(input, output);<br />

(* calculo de potencias con exponente natural *)<br />

var<br />

x, pot: real;<br />

n, i: integer;<br />

begin<br />

writeln(’** Calculo de la potencia x a la n’);<br />

writeln;<br />

write(’Entrar x (real): ’); readln(x);<br />

write(’Entrar n (entero positivo): ’); readln(n);<br />

pot := 1;<br />

for i := 1 to n do pot := pot * x;<br />

writeln;<br />

writeln(x, ’ a la ’, n:1, ’ es ’, pot);<br />

writeln; writeln(’** Fin **’)<br />

end.<br />

Problema 4.22: eolnprueba (pág. 38)<br />

program eolnprueba(input, output);<br />

(* prueba del funcionamiento de eoln *)<br />

var a: integer;<br />

begin


sumardatos (Problema 4.23) Pág. 181<br />

writeln(’** Prueba del funcionamiento de eoln’);<br />

writeln;<br />

a := 1; writeln(’El valor de a es ’, a:1);<br />

if (eoln) then a := 2 else a := 3;<br />

writeln(’Ahora el valor de a es ’, a:1);<br />

writeln; writeln(’** Fin **’)<br />

end.<br />

Problema 4.23: sumardatos (pág. 39)<br />

program sumardatos(input, output);<br />

(* Calcular la suma de datos entrados por terminal, donde<br />

el fin de datos se indica con sin datos *)<br />

var<br />

s, x: real;<br />

findatos: boolean;<br />

begin<br />

writeln(’** Calcular la suma de los datos entrados’);<br />

writeln(’ indicando el fin de datos con doble ’);<br />

writeln;<br />

s := 0; findatos := false; (* inicializacion *)<br />

repeat<br />

write(’Entrar un dato (fin = ): ’);<br />

if (eoln) then findatos := true<br />

else begin readln(x); s := s + x end<br />

until (findatos);<br />

readln; (* para leer el ultimo fin de datos,<br />

en este caso innecesario *)<br />

writeln;<br />

writeln(’La suma de los datos es ’, s);<br />

writeln; writeln(’** Fin **’)<br />

end.<br />

Problema 5.10: babilonico (pág. 47)<br />

program babilonico(input, output);<br />

(*<br />

Metodo babilonico o de Newton para aproximar<br />

la raiz cuadrada de un real positivo<br />

*)


Pág. 182 Programas mencionados<br />

const<br />

itmax = 10;<br />

tol = 10e-5;<br />

x0 = 1.0;<br />

var<br />

it: integer;<br />

a, x, y: real;<br />

begin<br />

(* carteles *)<br />

writeln(’** Metodo babilonico o de Newton para’);<br />

writeln(’ aproximar la raiz cuadrada del numero’);<br />

writeln(’ real positivo "a"’); writeln;<br />

(* datos de entrada *)<br />

write(’ Entrar a (> 0): ’); readln(a); writeln;<br />

(* inicializacion *)<br />

it := 1; x := x0; y := (x + a/x)/2;<br />

(* lazo principal *)<br />

while ((it tol)) do<br />

begin x := y; y := (x + a/x)/2; it := it + 1 end;<br />

(* salida *)<br />

writeln(’Iteraciones realizadas: ’, it);<br />

if (it > itmax) then<br />

writeln(’** Iteraciones maximas excedidas **’);<br />

writeln(’Solucion aproximada obtenida: ’, y:20:10);<br />

(* fin *)<br />

writeln; writeln(’** Fin **’)<br />

end.<br />

Problema 5.15: euclides (pág. 51)<br />

program euclides(input, output);<br />

(*<br />

Algoritmo de Euclides, usando restos<br />

en vez de restas sucesivas<br />

*)<br />

var a, b, r: integer;<br />

begin<br />

(* cartel de info *)<br />

writeln(’** Algoritmo de Euclides para encontrar el ’);<br />

write(’ maximo comun divisor entre dos enteros, ’);<br />

writeln(’ mcd(a,b)’); writeln;


factoresprimos (Problema 5.20) Pág. 183<br />

writeln(’Nota: mcd(0,0) definido como 0’); writeln;<br />

(* datos de entrada *)<br />

write(’ Entrar a: ’); readln(a);<br />

write(’ Entrar b: ’); readln(b); writeln;<br />

(* inicializacion: trabajar con no negativos *)<br />

if (a < 0) then a := -a;<br />

if (b < 0) then b := -b;<br />

(* lazo principal *)<br />

while (b 0) do begin r := a mod b; a := b; b := r end;<br />

(* salida *)<br />

writeln(’El maximo comun divisor es: ’, a:2);<br />

(* fin *)<br />

writeln; writeln(’** Fin **’)<br />

end.<br />

Problema 5.20: factoresprimos (pág. 53)<br />

program factoresprimos(input, output);<br />

(* Encontrar factores primos de un entero positivo *)<br />

var<br />

a, b, s, t: integer;<br />

tesprimo: boolean;<br />

begin<br />

(* carteles *)<br />

writeln(’** Encontrar los factores primos de un entero’);<br />

writeln;<br />

(* datos de entrada *)<br />

write(’Entrar el entero (> 1): ’); readln(a); writeln;<br />

(* inicializacion, lazo e impresion *)<br />

t := a; tesprimo := false; b := 2;<br />

repeat<br />

s := trunc(sqrt(t));<br />

while (((t mod b) 0) and (b s) then tesprimo := true<br />

else begin (* b es factor de t (=> de a) *)<br />

write(b:1, ’, ’);<br />

t := t div b<br />

end<br />

until tesprimo;<br />

writeln(t:1);


Pág. 184 Programas mencionados<br />

(* fin *)<br />

writeln; writeln(’** Fin **’)<br />

end.<br />

Problema 6.1: unidades (pág. 56)<br />

program unidades(input, output);<br />

(*<br />

contar cuantos de los numeros ingresados<br />

terminan en 0,1,...,9<br />

*)<br />

var<br />

dato, digito: integer;<br />

terminaen: array[0..9] of integer;<br />

begin<br />

writeln(’** Contar cuantos de los numeros ingresados’);<br />

writeln(’ terminan en 0,1,...,9’);<br />

writeln;<br />

(* inicializar el arreglo *)<br />

for digito := 0 to 9 do terminaen[digito] := 0;<br />

(* entrar datos *)<br />

write(’Entrar un entero (fin = ): ’);<br />

while (not eoln) do begin<br />

readln(dato);<br />

digito := abs(dato) mod 10;<br />

terminaen[digito] := terminaen[digito] + 1;<br />

write(’Entrar un entero (fin = ): ’)<br />

end;<br />

readln;<br />

(* salida *)<br />

writeln;<br />

writeln(’ Digito numero de datos’);<br />

for digito := 0 to 9 do<br />

writeln( digito:4, terminaen[digito]:20);<br />

writeln; writeln(’** Fin **’)<br />

end.<br />

Problema 6.2: busquedalineal (pág. 57)<br />

program busquedalineal(input, output);<br />

(*<br />

Dado un arreglo a1, a2,... y un elemento x, ver<br />

si x = ai para algun i.


usquedalineal (Problema 6.2) Pág. 185<br />

*)<br />

const MAXN = 20;<br />

var<br />

i, ndatos, x: integer;<br />

findatos, seencontro: boolean;<br />

a: array[1..MAXN] of integer;<br />

begin<br />

(* carteles *)<br />

writeln(’** Dado un arreglo a1, a2,...’);<br />

writeln(’ ver si x = ai para algun i’);<br />

writeln;<br />

(* leer el arreglo *)<br />

write(’Entrar enteros, senialando fin’);<br />

writeln(’ con sin datos.’);<br />

ndatos := 1; findatos := false;<br />

repeat<br />

write(’Entrar el dato ’, ndatos:1);<br />

write(’ (fin = ): ’);<br />

if (eoln) then begin (* no hay mas datos *)<br />

findatos := true; readln end<br />

else begin (* nuevo dato *)<br />

readln(a[ndatos]); (* incorporarlo al arreglo *)<br />

ndatos := ndatos + 1 (* para el proximo *)<br />

end<br />

until (findatos);<br />

ndatos := ndatos - 1;<br />

(* imprimir el arreglo *)<br />

writeln(’El arreglo es: ’); writeln;<br />

for i := 1 to ndatos do begin<br />

write(a[i]:10);<br />

if ((i mod 5) = 0) then writeln<br />

end;<br />

if ((ndatos mod 5) 0) then writeln;<br />

writeln;<br />

(* elemento a buscar *)<br />

write(’Entrar el entero x a buscar: ’); readln(x);<br />

writeln;<br />

(* lazo principal: suponemos ndatos > 1 *)<br />

i := 0;<br />

repeat<br />

i := i + 1;<br />

seencontro := (a[i] = x)<br />

until (seencontro or (i = ndatos));


Pág. 186 Programas mencionados<br />

(* resultados y fin *)<br />

if (seencontro)<br />

then writeln(’Se encontro en la posicion ’, i:1)<br />

else<br />

writeln(’No se encontro’);<br />

writeln; writeln(’** Fin **’)<br />

end.<br />

Problema 6.5: eratostenes (pág. 59)<br />

program eratostenes(input, output);<br />

(* Criba de Eratostenes para encontrar primos *)<br />

const MAXP = 10000; (* maximo primo *)<br />

var<br />

p, i, cuenta, k: integer;<br />

esprimo: array[2..MAXP] of boolean;<br />

begin<br />

writeln(’** Criba de Eratostenes para encontrar’);<br />

writeln(’ los primos entre 2 y ’, MAXP:1);<br />

writeln;<br />

(* inicializacion *)<br />

for i := 2 to MAXP do esprimo[i] := true;<br />

cuenta := 0;<br />

(* lazo principal *)<br />

for i := 2 to MAXP do<br />

if (esprimo[i]) then begin (* nuevo primo *)<br />

cuenta := cuenta + 1;<br />

p := i; (* el nuevo primo *)<br />

(* ahora eliminar multiplos de p *)<br />

k := p + p;<br />

while (k


flaviojosefo1 (Problema 6.7) Pág. 187<br />

Problema 6.7: flaviojosefo1 (pág. 61)<br />

program flaviojosefo1(input, output);<br />

(* Problema de Flavio Josefo con arreglos *)<br />

const MAXN = 100;<br />

var<br />

m, n, i, vuelta, cuenta: integer;<br />

vive: array[1..MAXN] of boolean;<br />

flavio: array[1..MAXN] of integer;<br />

begin<br />

(* carteles *)<br />

writeln(’** Problema de Flavio Josefo’);<br />

writeln;<br />

writeln(’En un circulo de n, se van eliminando de a m’);<br />

writeln;<br />

(* datos *)<br />

write(’Entrar n: ’); readln(n);<br />

write(’Entrar m: ’); readln(m);<br />

writeln;<br />

(* inicializacion *)<br />

for i := 1 to n do vive[i] := true; (* aun vive *)<br />

i := 0;<br />

(* parte principal *)<br />

for vuelta := 1 to n-1 do begin<br />

(* en cada vuelta, i es el ultimo eliminado *)<br />

cuenta := 0; (* contamos m sobrevivientes desde i *)<br />

repeat<br />

if (i n) then i := i + 1 else i := 1;<br />

if (vive[i]) then cuenta := cuenta + 1<br />

until (cuenta = m);<br />

vive[i] := false; (* lo eliminamos *)<br />

flavio[vuelta] := i<br />

end;<br />

(* encontrar el sobreviviente *)<br />

i := 0; repeat i := i + 1 until vive[i];<br />

flavio[n] := i;<br />

(* salida y fin *)<br />

writeln(’ La permutacion de Flavio Josefo es’); writeln;<br />

for i := 1 to n do begin<br />

write(flavio[i]:5);<br />

if ((i mod 10) = 0) then writeln<br />

end;


Pág. 188 Programas mencionados<br />

if ((n mod 10) 0) then writeln;<br />

writeln; writeln(’** Fin **’)<br />

end.<br />

Problema 7.1: potencias2 (pág. 67)<br />

program potencias2(input, output);<br />

(* Comparar x a la n con y a la m. *)<br />

var<br />

n, m, k: integer;<br />

x, y, xn, ym: real;<br />

begin<br />

(* carteles iniciales *)<br />

writeln(’** Comparar x a la n con y a la m’);<br />

writeln;<br />

(* entrar datos *)<br />

write(’Entrar x (real): ’); readln(x);<br />

write(’Entrar n (entero): ’); readln(n);<br />

write(’Entrar y (real): ’); readln(y);<br />

write(’Entrar m (entero): ’); readln(m);<br />

(* calcular x a la n, y a la m *)<br />

xn := 1; for k := 1 to n do xn := xn * x;<br />

ym := 1; for k := 1 to m do ym := ym * y;<br />

(* comparar e imprimir *)<br />

write(x:10:5, ’ a la ’, n:1);<br />

if (xn < ym) then write(’ es menor que ’)<br />

else if (xn > ym) then write(’ es mayor que ’)<br />

else write(’ es igual a ’);<br />

writeln(y:10:5, ’ a la ’, m:1);<br />

(* cartel final *)<br />

writeln; writeln(’** Fin **’)<br />

end.<br />

Problema 7.2: potencias3 (pág. 68)<br />

program potencias3(input, output);<br />

(*<br />

Primer ejemplo de uso de funciones,<br />

definiendo potencia(x,n) para comparar<br />

x a la n con y a la m.<br />

*)<br />

var


iseccion (Problema 7.3) Pág. 189<br />

n, m: integer;<br />

x, y, xn, ym: real;<br />

function potencia(a: real; b: integer): real;<br />

var k: integer; z: real;<br />

begin<br />

z := 1;<br />

for k := 1 to b do z := z * a;<br />

potencia := z<br />

end;<br />

begin<br />

(* carteles iniciales *)<br />

writeln(’** Comparar x a la n con y a la m’);<br />

writeln;<br />

(* entrar datos *)<br />

write(’Entrar x (real): ’); readln(x);<br />

write(’Entrar n (entero): ’); readln(n);<br />

write(’Entrar y (real): ’); readln(y);<br />

write(’Entrar m (entero): ’); readln(m);<br />

(* calcular x a la n, y a la m *)<br />

xn := potencia(x,n); ym := potencia(y,m);<br />

(* comparar e imprimir *)<br />

write(x:10:5, ’ a la ’, n:1);<br />

if (xn < ym) then write(’ es menor que ’)<br />

else if (xn > ym) then write(’ es mayor que ’)<br />

else write(’ es igual a ’);<br />

writeln(y:10:5, ’ a la ’, m:1);<br />

(* cartel final *)<br />

writeln; writeln(’** Fin **’)<br />

end.<br />

Problema 7.3: biseccion (pág. 71)<br />

program biseccion(input, output);<br />

(*<br />

Metodo de biseccion para encontrar raices de funciones<br />

reales y continuas.<br />

*)<br />

const<br />

Cambiar la funcion cuando se investigue otra ecuacion.<br />

Version simplificada que se va mejorando<br />

en los trabajos practicos.


Pág. 190 Programas mencionados<br />

dify = 1.0e-6; (* tolerancia en y *)<br />

maxiter = 30; (* maximo numero de iteraciones *)<br />

var<br />

iter: integer;<br />

poco, mucho, x, (* valores en x *)<br />

ypoco, ymucho, y (* correspondientes valores en y *)<br />

: real;<br />

seguir (* indica si seguir iterando *)<br />

: boolean;<br />

(* funcion para la que se quiere encontrar una raiz *)<br />

function f(x: real): real;<br />

begin<br />

f := x * (x + 1) * (x + 2) * (x - 4/3)<br />

end;<br />

begin<br />

(* carteles *)<br />

writeln(’** Metodo de biseccion para encontrar raices’);<br />

writeln;<br />

write(’Implementacion para ’);<br />

writeln(’f(x) = x (x + 1) (x + 2) (x - 4/3)’);<br />

writeln;<br />

(* datos de entrada *)<br />

write(’ Entrar cota inferior para x: ’); readln(poco);<br />

write(’ Entrar cota superior para x: ’); readln(mucho);<br />

writeln;<br />

(* inicializacion *)<br />

ypoco := f(poco); ymucho := f(mucho);<br />

seguir := (ypoco * ymucho < 0);<br />

iter := 0;<br />

(* lazo principal *)<br />

while (seguir) do begin<br />

iter := iter + 1;<br />

x := (poco + mucho) / 2;<br />

y := f(x);<br />

if (abs(y) < dify) then seguir := false<br />

else if (iter = maxiter) then seguir := false<br />

else if (y * ypoco < 0) then mucho := x<br />

else begin poco := x; ypoco := y end<br />

end;<br />

(* salida *)<br />

if (iter > 0) then (* hay una solucion, aceptable o no *)<br />

begin<br />

writeln(’ Solucion obtenida: ’, x);


tablaseno2 (Problema 7.6) Pág. 191<br />

writeln(’ Valor de f: ’, y);<br />

writeln(’ Iteraciones realizadas: ’, iter:1);<br />

if (abs(y) > dify) then begin<br />

writeln;<br />

write(’* La respuesta puede no estar ’);<br />

writeln(’cerca de una raiz *’)<br />

end<br />

end;<br />

(* fin *)<br />

writeln; writeln(’** Fin **’)<br />

end.<br />

Problema 7.6: tablaseno2 (pág. 76)<br />

program tablaseno2(input, output);<br />

(* hacer una tabla del seno donde los angulos<br />

estan dados en grados *)<br />

const pi = 3.14159265;<br />

var inicial, final, incremento: integer;<br />

procedure cartelesiniciales;<br />

begin<br />

writeln(’** Hacer una tabla del seno dando valores’);<br />

writeln(’ inicial, final, y del incremento (en grados).’);<br />

writeln<br />

end;<br />

procedure leerdatos;<br />

begin<br />

write(’Entrar el valor inicial (en grados): ’);<br />

readln(inicial);<br />

write(’Entrar el valor final (en grados): ’);<br />

readln(final);<br />

write(’Entrar el valor de incremento (en grados): ’);<br />

readln(incremento);<br />

writeln<br />

end;<br />

procedure imprimirtabla;<br />

var<br />

grados: integer;<br />

radianes: real;<br />

begin<br />

writeln(’Angulo Seno’);<br />

grados := inicial;<br />

while (grados


Pág. 192 Programas mencionados<br />

end;<br />

writeln(grados:5, sin(radianes):15:5);<br />

grados := grados + incremento<br />

end<br />

procedure cartelfinal;<br />

begin writeln; writeln(’** Fin **’) end;<br />

begin<br />

cartelesiniciales;<br />

leerdatos;<br />

imprimirtabla;<br />

cartelfinal<br />

end.<br />

Problema 7.7: intercambio (pág. 77)<br />

program intercambio(input, output);<br />

(* prueba de intercambio de valores entre variables *)<br />

var x, y: integer;<br />

procedure incorrecto(a, b: integer);<br />

(* a, b pasados por valor *)<br />

var t: integer;<br />

begin t := a; a := b; b := t end;<br />

begin<br />

writeln(’** Prueba de intercambio de variables’);<br />

writeln;<br />

x := 5; y := 2;<br />

writeln(’Antes: x = ’, x:2, ’, y = ’, y:2);<br />

incorrecto(a,b);<br />

writeln(’Despues: x = ’, x:2, ’, y = ’, y:2);<br />

writeln; writeln(’** Fin **’)<br />

end.<br />

Problema 8.2: maximocaracter (pág. 83)<br />

program maximocaracter(input, output);<br />

(*<br />

Encontrar el maximo caracter en<br />

un renglon entrado por terminal<br />

*)<br />

const MAXC = 255; (* maximo tamanio de arreglo *)<br />

type tiporenglon = array[1..MAXC] of char;


maximocaracter (Problema 8.2) Pág. 193<br />

var<br />

p, (* posicion en el renglon *)<br />

n (* numero de caracteres en el renglon *)<br />

: integer;<br />

t (* el caracter de maximo ordinal *)<br />

: char;<br />

renglon (* el renglon a procesar *)<br />

: tiporenglon;<br />

procedure leerrenglon( var s: tiporenglon; var n: integer);<br />

(* aqui suponemos renglon: array[1..MAXC] of char *)<br />

begin<br />

write(’Escribir algo ’);<br />

writeln(’(no mas de ’, MAXC:1, ’ caracteres.):’);<br />

n := 0;<br />

while ((not eoln) and (n < MAXC)) do begin<br />

n := n + 1; read(s[n]) (* aca es read y no readln *)<br />

end;<br />

readln (* para leer el ultimo fin de linea *)<br />

end;<br />

procedure escribirrenglon( s: tiporenglon; n: integer);<br />

var i: integer;<br />

begin<br />

for i := 1 to n do write(s[i]);<br />

writeln<br />

end;<br />

procedure max(<br />

var t: tiporenglon;<br />

long: integer;<br />

var tmax: char;<br />

var indice: integer);<br />

var i: integer;<br />

begin (* suponemos long positivo *)<br />

tmax := t[1]; indice := 1;<br />

for i := 2 to long do<br />

if (t[i] > tmax) then begin<br />

tmax := t[i]; indice := i end<br />

end; (* fin de procedure max *)<br />

begin<br />

(* titulo *)<br />

write(’** Programa para encontrar el maximo en un ’);<br />

write(’ arreglo de caracteres entrado por terminal’);<br />

writeln;<br />

(* entrada *)<br />

leerrenglon(renglon, n);


Pág. 194 Programas mencionados<br />

(* verificacion *)<br />

writeln; writeln(’El renglon entrado es: ’);<br />

writeln;<br />

escribirrenglon(renglon, n);<br />

(* procesamiento *)<br />

max(renglon, n, t, p);<br />

(* salida y fin *)<br />

writeln; write(’El maximo es "’, t, ’",’);<br />

writeln(’ alcanzado en la posicion ’, p:1);<br />

writeln; writeln(’** Fin **’)<br />

end.<br />

Problema 8.8: deconsolaaarchivo (pág. 88)<br />

program deconsolaaarchivo(input, output);<br />

(*<br />

Copiar de consola a archivo<br />

*)<br />

En la entrada no deben haber renglones "vacios"<br />

var<br />

archivo: text;<br />

c: char;<br />

nombre: string;<br />

begin<br />

(* carteles *)<br />

writeln(’** Copiar de consola a archivo’);<br />

writeln;<br />

(* nombre de archivo a escribir *)<br />

writeln(’ Entrar el nombre del archivo a escribir’);<br />

writeln(’ Atencion: si ya existe se borra!’);<br />

write(’ Archivo: ’); readln(nombre);<br />

rewrite(archivo, nombre);<br />

(* copiar de consola a archivo *)<br />

writeln;<br />

write(’Entrar datos en ’, nombre);<br />

writeln(’ (Fin con retorno "vacio")’);<br />

while (not eoln) do begin<br />

repeat<br />

read(c); write(archivo, c)<br />

until eoln;<br />

readln;<br />

writeln(archivo)


dearchivoaconsola (Problema 8.8) Pág. 195<br />

end;<br />

close(archivo);<br />

(* Fin *)<br />

writeln; writeln(’** Fin **’)<br />

end.<br />

Problema 8.8: dearchivoaconsola (pág. 88)<br />

program dearchivoaconsola(input, output);<br />

(* Copiar de archivo a consola *)<br />

var<br />

archivo: text;<br />

c: char;<br />

nombre: string;<br />

begin<br />

(* carteles *)<br />

writeln(’** Copiar de archivo a consola’);<br />

writeln;<br />

(* nombre de archivo a leer *)<br />

write(’ Entrar el nombre del archivo a leer: ’);<br />

readln(nombre);<br />

reset(archivo, nombre);<br />

writeln;<br />

(* copiar datos en pantalla *)<br />

writeln(’Estos son los datos en ’, nombre, ’:’);<br />

writeln;<br />

while not eof(archivo) do<br />

if (eoln(archivo)) then begin<br />

readln(archivo); writeln end<br />

else begin<br />

read(archivo, c); write(c) end;<br />

close(archivo);<br />

(* Fin *)<br />

writeln; writeln(’** Fin **’)<br />

end.<br />

Problema 9.1: dado (pág. 92)<br />

program dado(input, output);<br />

(*<br />

Simular tirar un dado.


Pág. 196 Programas mencionados<br />

*)<br />

Usa la sentencia "random" de Turbo Pascal.<br />

var d: integer;<br />

begin<br />

writeln(’** Simular tirar un dado’);<br />

writeln;<br />

randomize;<br />

d := 1 + random(6);<br />

writeln(’El dado que salio es ’, d:1);<br />

writeln;<br />

writeln(’** para Fin **’); readln<br />

end.<br />

Problema 9.2: dados (pág. 92)<br />

program dados(input, output);<br />

(*<br />

Contar las veces que se tira un dado<br />

hasta que aparece un numero prefijado.<br />

*)<br />

Usa la sentencia "random" de Turbo Pascal.<br />

var numero, tiros: integer;<br />

begin<br />

writeln(’** Contar las veces que se tira un dado’);<br />

writeln(’ hasta que aparece un numero prefijado’);<br />

writeln;<br />

write(’Entrar el valor que debe aparecer ’);<br />

write(’(entre 1 y 6): ’);<br />

readln(numero);<br />

randomize;<br />

tiros := 0;<br />

repeat tiros := tiros + 1<br />

until (1 + random(6) = numero);<br />

writeln;<br />

write(’El numero ’, numero:1);<br />

writeln(’ tardo ’, tiros, ’ tiros hasta aparecer.’);<br />

writeln;<br />

writeln(’** para Fin **’); readln


arbolbinario (Problema 12.6) Pág. 197<br />

end.<br />

Problema 12.6: arbolbinario (pág. 129)<br />

program arbolbinario(input, output);<br />

(*<br />

Arbol binario parcialmente ordenado guardando los<br />

nodos en un arreglo. Variante de las versiones con<br />

punteros de Wirth87, p. 210, y K&R, p. 153.<br />

*)<br />

Aplicacion al problema de hacer un listado<br />

ordenado de caracteres entrados por terminal,<br />

contando las apariciones.<br />

const<br />

MAXN = 100; (* maximo numero de datos a guardar *)<br />

nada = 0; (* para cuando no hay hijos *)<br />

type<br />

indice = integer; (* para senialar los indices *)<br />

tipodato = char; (* el tipo de datos a ingresar *)<br />

nodo = record<br />

llave: tipodato; (* dato *)<br />

cuenta: integer; (* veces que aparecio *)<br />

izquierda: indice; (* hijo a la izquierda *)<br />

derecha: indice (* hijo a la derecha *)<br />

end;<br />

tipoarbol = array[1..MAXN] of nodo;<br />

var<br />

narbol: integer; (* numero de nodos en el arbol *)<br />

arbol: tipoarbol; (* el arbol! *)<br />

raiz: indice; (* donde empieza el arbol *)<br />

dato: tipodato; (* dato leido *)<br />

function entrardatos(var dato: tipodato): boolean;<br />

begin<br />

write(’Entrar el numero (fin = ): ’);<br />

if (eoln) then<br />

begin entrardatos := false; readln end<br />

else<br />

begin entrardatos := true; readln(dato) end<br />

end;<br />

procedure binario(x: tipodato; var p: indice);<br />

var y: tipodato;<br />

begin<br />

if (p = nada) then begin (* nueva entrada *)<br />

narbol := narbol + 1;


Pág. 198 Programas mencionados<br />

p := narbol;<br />

with arbol[p] do begin<br />

llave := x; cuenta := 1;<br />

izquierda := nada; derecha := nada<br />

end<br />

end<br />

else begin<br />

y := arbol[p].llave;<br />

if (x = y) then (* dato existente *)<br />

arbol[p].cuenta := arbol[p].cuenta + 1<br />

else if (x < y) then (* ir a la izquierda *)<br />

binario(x, arbol[p].izquierda)<br />

else (* ir a la derecha *)<br />

binario(x, arbol[p].derecha)<br />

end<br />

end;<br />

procedure enorden(w: indice);<br />

begin<br />

if (w nada) then<br />

with arbol[w] do begin<br />

enorden(izquierda);<br />

writeln(llave:10, cuenta:20);<br />

enorden(derecha)<br />

end<br />

end;<br />

begin<br />

(* carteles *)<br />

writeln(’** Arbol binario’); writeln;<br />

write(’ Entrar caracteres y contar’);<br />

writeln(’ las veces que aparecieron’);<br />

writeln;<br />

(* inicializacion *)<br />

narbol := 0;<br />

raiz := nada;<br />

(* lectura de datos y construccion del arbol *)<br />

while (entrardatos(dato)) do binario(dato, raiz);<br />

(* salida y fin *)<br />

writeln;<br />

writeln(’Impresion en orden:’);<br />

writeln;<br />

writeln(’ Caracter Cantidad de Apariciones’);<br />

enorden(raiz);<br />

writeln; writeln(’** Fin **’)<br />

end.


anchoprimero (Problema 13.4) Pág. 199<br />

Problema 13.4: anchoprimero (pág. 139)<br />

program anchoprimero(input, output);<br />

(*<br />

Recorrer un grafo a lo ancho para<br />

determinar un arbol de expansion.<br />

*)<br />

Entrada: un grafo dado por lista de aristas y el<br />

numero de vertices, n.<br />

Los vertices son todos los enteros positivos<br />

entre 1 y n. Si alguno no aparece en ninguna<br />

arista, es un vertice aislado.<br />

Salida: las aristas del arbol y el orden en que<br />

fueron visitados los vertices.<br />

Si el arbol no es conexo, se imprime un mensaje.<br />

Implementacion con una cola fifo de vertices a visitar.<br />

const<br />

MAXN = 20; (* maximo numero de vertices *)<br />

MAXM = 100; (* maximo numero de aristas *)<br />

type<br />

arreglodevertices = array[1..MAXN] of integer;<br />

tipoarista = record i, j: integer end;<br />

arreglodearistas = array[1..MAXM] of tipoarista;<br />

matrizNN = array[1..MAXN,1..MAXN] of integer;<br />

var<br />

ngrafo, mgrafo, nvisitado, marbol: integer;<br />

padre, visitado: arreglodevertices;<br />

aristasgrafo, aristasarbol: arreglodearistas;<br />

adyacencias: matrizNN;<br />

function nuevaarista: boolean;<br />

begin<br />

writeln;<br />

write(’ Entrar la arista ’, mgrafo:2);<br />

writeln(’ (fin = ):’);<br />

write(’ Entrar el primer vertice: ’);<br />

if (not eoln) then begin<br />

nuevaarista := true;<br />

with aristasgrafo[mgrafo] do begin<br />

read(i);<br />

write(’ Entrar el segundo vertice: ’);<br />

readln(j)<br />

end (* with *)<br />

end (* if not eoln *)


Pág. 200 Programas mencionados<br />

else begin nuevaarista := false; readln end<br />

end;<br />

procedure entrardatos;<br />

begin<br />

(* vertices *)<br />

writeln;<br />

write(’Entrar el numero de vertices: ’);<br />

readln(ngrafo);<br />

(* aristas *)<br />

writeln;<br />

writeln(’Entrar las aristas:’);<br />

mgrafo := 1;<br />

while (nuevaarista) do mgrafo := mgrafo + 1;<br />

mgrafo := mgrafo - 1<br />

end;<br />

procedure escribiraristas( a: arreglodearistas; m: integer);<br />

const maxrenglon = 10; (* maxima cantidad por renglon *)<br />

var k: integer;<br />

begin<br />

for k := 1 to m do begin<br />

write(’ (’, a[k].i:1, ’,’, a[k].j:1, ’)’);<br />

if ((k mod maxrenglon) = 0) then writeln<br />

end;<br />

if ((m mod maxrenglon) > 0) then writeln<br />

end;<br />

procedure dearistasaadyacencias;<br />

var i, j, k: integer;<br />

begin<br />

for i := 1 to ngrafo do<br />

for j := 1 to ngrafo do<br />

adyacencias[i,j] := 0;<br />

for k := 1 to mgrafo do<br />

with aristasgrafo[k] do begin<br />

adyacencias[i,j] := 1; adyacencias[j,i] := 1<br />

end<br />

end;<br />

procedure ancho;<br />

var<br />

i, j, ppo, fin: integer;<br />

avisitar: arreglodevertices;<br />

begin<br />

(* inicializacion *)<br />

dearistasaadyacencias;<br />

for i := 1 to ngrafo do padre[i] := 0;<br />

nvisitado := 0;


anchoprimero (Problema 13.4) Pág. 201<br />

(* al principio solo esta 1 *)<br />

padre[1] := 1;<br />

ppo := 1; fin := 1; (* los extremos de la cola *)<br />

avisitar[1] := 1;<br />

while (ppo 0) and (padre[j] = 0)) then<br />

begin (* agregar j a la cola *)<br />

padre[j] := i;<br />

fin := fin + 1;<br />

avisitar[fin] := j<br />

end (* if adyacencias > 0 *)<br />

end (* while *)<br />

end;<br />

procedure hacerarbol;<br />

(* Encontrar las aristas del arbol generado usando padre *)<br />

var k: integer;<br />

begin<br />

marbol := 0;<br />

for k := 2 to nvisitado do (* 1 es "raiz" *)<br />

if (padre[k] > 0) then begin<br />

marbol := marbol + 1;<br />

with aristasarbol[marbol] do begin<br />

i := k; j := padre[k] end<br />

end<br />

end;<br />

procedure escribirvisitado;<br />

const maxrenglon = 10; (* maxima cantidad por renglon *)<br />

var i: integer;<br />

begin<br />

for i := 1 to nvisitado do begin<br />

write(visitado[i]:5);<br />

if ((i mod maxrenglon) = 0) then writeln<br />

end;<br />

if ((nvisitado mod maxrenglon) 0) then writeln<br />

end;<br />

begin<br />

(* Carteles *)


Pág. 202 Programas mencionados<br />

writeln(’** Recorrer un grafo a lo ancho para determinar’);<br />

writeln(’ un arbol de expansion entrando las aristas,’);<br />

writeln(’ tomando como raiz a 1.’);<br />

writeln;<br />

(* Datos e inicializacion *)<br />

entrardatos;<br />

writeln;<br />

writeln(’ Las aristas originales son:’);<br />

escribiraristas( aristasgrafo, mgrafo);<br />

(* Procesamiento y salida *)<br />

ancho;<br />

hacerarbol;<br />

writeln;<br />

writeln(’ Las aristas del arbol resultante son:’);<br />

escribiraristas(aristasarbol, marbol);<br />

writeln;<br />

writeln(’Los vertices se visitaron en el siguiente orden:’);<br />

escribirvisitado;<br />

if (nvisitado < ngrafo) then begin<br />

writeln; writeln(’** El grafo no es conexo **’) end;<br />

(* Fin *)<br />

writeln; writeln(’** Fin **’)<br />

end.<br />

Problema 13.8: dijkstra (pág. 144)<br />

program dijkstra(input, output);<br />

(*<br />

Implementacion del algoritmo de Dijkstra para<br />

encontrar un camino mas corto entre dos vertices<br />

en un grafo pesado.<br />

*)<br />

Entrada: un grafo pesado dado por lista de aristas,<br />

el numero de vertices, n, y los vertices a unir, s y t.<br />

Los vertices son todos los enteros positivos<br />

entre 1 y n. Si alguno no aparece en ninguna<br />

arista, es un vertice aislado.<br />

Los pesos de cada arista son enteros positivos.<br />

Salida: la longitud de un camino mas corto, y el<br />

camino que lo realiza como lista de vertices. Si<br />

los vertices no se pueden conectar, se imprime un<br />

mensaje.<br />

const<br />

MAXN = 20; (* maximo numero de vertices *)


dijkstra (Problema 13.8) Pág. 203<br />

MAXM = 100; (* maximo numero de aristas *)<br />

type<br />

costo = integer; (* podria ser real *)<br />

arreglodevertices = array[1..MAXN] of integer;<br />

tipoarista = record<br />

i, j: integer; (* los extremos *)<br />

w: costo (* el costo *)<br />

end;<br />

arreglodearistas = array[1..MAXM] of tipoarista;<br />

matrizNN = array[1..MAXN,1..MAXN] of costo;<br />

var<br />

ngrafo, mgrafo, s, t: integer;<br />

infinito: costo;<br />

padre: arreglodevertices;<br />

dist: array[1..MAXN] of costo;<br />

aristasgrafo: arreglodearistas;<br />

costos: matrizNN;<br />

function nuevaarista: boolean;<br />

begin<br />

writeln;<br />

write(’ Entrar la arista ’, mgrafo:2);<br />

writeln(’ (fin = ): ’);<br />

write(’ Entrar el primer vertice: ’);<br />

if (not eoln) then begin<br />

nuevaarista := true;<br />

with aristasgrafo[mgrafo] do begin<br />

read(i);<br />

write(’ Entrar el segundo vertice: ’);<br />

read(j);<br />

write(’ Entrar el costo (entero > 0): ’);<br />

readln(w);<br />

infinito := infinito + w<br />

end (* with *)<br />

end (* if not eoln *)<br />

else begin nuevaarista := false; readln end<br />

end;<br />

procedure entrardatos;<br />

begin<br />

(* vertices *)<br />

writeln;<br />

write(’Entrar el numero de vertices: ’);<br />

readln(ngrafo);<br />

(* aristas *)<br />

writeln;<br />

writeln(’Entrar las aristas:’);<br />

mgrafo := 1; infinito := 1;


Pág. 204 Programas mencionados<br />

while (nuevaarista) do mgrafo := mgrafo + 1;<br />

mgrafo := mgrafo - 1;<br />

(* vertices a unir *)<br />

writeln;<br />

write(’Entrar el vertice de partida: ’); readln(s);<br />

write(’Entrar el vertice de llegada: ’); readln(t)<br />

end;<br />

procedure escribiraristas(a: arreglodearistas; m: integer);<br />

var k: integer;<br />

begin<br />

writeln;<br />

writeln(’ Vertices Costo’);<br />

for k := 1 to m do<br />

with a[k] do writeln(i:5, j:6, w:9)<br />

end;<br />

procedure dearistasacostos;<br />

var i, j, k: integer;<br />

begin<br />

for i := 1 to ngrafo do<br />

for j := 1 to ngrafo do<br />

costos[i,j] := infinito;<br />

for k := 1 to mgrafo do<br />

with aristasgrafo[k] do begin<br />

costos[i,j] := w; costos[j,i] := w<br />

end<br />

end;<br />

procedure mascorto;<br />

var<br />

i, j, k, kmin, hay: integer;<br />

d, dmin: costo;<br />

avisitar: arreglodevertices;<br />

begin<br />

(* inicializacion *)<br />

dearistasacostos;<br />

for i := 1 to ngrafo do begin<br />

padre[i] := 0; dist[i] := infinito end;<br />

(* s es la "raiz" y el unico en la cola al comenzar *)<br />

padre[s] := s; dist[s] := 0; hay := 1; avisitar[1] := s;<br />

repeat (* aca hay > 0 *)<br />

(* nuevo i: el de minima distancia en la cola *)<br />

kmin := hay; i := avisitar[hay]; dmin := dist[i];<br />

for k := 1 to hay - 1 do begin<br />

j := avisitar[k]; d := dist[j];<br />

if (d < dmin) then begin


dijkstra (Problema 13.8) Pág. 205<br />

kmin := k; i := j; dmin := d end<br />

end;<br />

(* ahora tenemos el nuevo i *)<br />

if (i t) then begin<br />

(* poner i al fin de la cola y sacarlo *)<br />

if (kmin < hay) then avisitar[kmin] := avisitar[hay];<br />

hay := hay - 1;<br />

(* examinar vecinos de i *)<br />

for j := 1 to ngrafo do<br />

if (dist[i] + costos[i,j] < dist[j]) then begin<br />

(* si j no se agrego, agregarlo a la cola *)<br />

if (dist[j] = infinito) then begin<br />

hay := hay + 1; avisitar[hay] := j end;<br />

(* actualizar dist y padre *)<br />

dist[j] := dist[i] + costos[i,j];<br />

padre[j] := i<br />

end<br />

end (* if i t *)<br />

until ((i = t) or (hay = 0))<br />

end;<br />

procedure hacercamino;<br />

(* Construir e imprimir el camino mas corto usando padre. *)<br />

const maxrenglon = 10; (* maxima cantidad / renglon *)<br />

var<br />

i, ncamino: integer;<br />

camino: arreglodevertices;<br />

begin<br />

ncamino := 1; camino[1] := t; i := t;<br />

while (i s) do begin<br />

i := padre[i];<br />

ncamino := ncamino + 1;<br />

camino[ncamino] := i<br />

end;<br />

(* imprimir "dando vuelta" el arreglo *);<br />

writeln(’ Los vertices en el camino son:’);<br />

for i := ncamino downto 1 do begin<br />

write(camino[i]:5);<br />

if ((i mod maxrenglon) = 0) then writeln<br />

end;<br />

if ((ncamino mod maxrenglon) 0) then writeln<br />

end;<br />

begin<br />

(* Carteles *)<br />

writeln(’** Busqueda del camino mas corto entre dos’);<br />

writeln(’ vertices con el algoritmo de Dijkstra.’);<br />

writeln(’ El grafo se entra mediante lista de aristas.’);<br />

(* Datos e inicializacion *)


Pág. 206 Programas mencionados<br />

entrardatos;<br />

writeln; writeln(’Las aristas del grafo son:’);<br />

escribiraristas(aristasgrafo, mgrafo);<br />

writeln; writeln(’Los vertices a unir son:’);<br />

writeln(’ partida: ’, s:1);<br />

writeln(’ llegada: ’, t:1);<br />

(* Procesamiento y salida *)<br />

mascorto;<br />

writeln;<br />

if (padre[t] > 0) then begin<br />

writeln(’ La longitud del camino es ’, dist[t]:1);<br />

hacercamino<br />

end<br />

else begin<br />

writeln;<br />

writeln(’** ’, s:1, ’ y ’ , t:1, ’ no se conectan **’)<br />

end;<br />

(* Fin *)<br />

writeln; writeln(’** Fin **’)<br />

end.<br />

Problema 14.1: listas (pág. 159)<br />

program listas(input, output);<br />

(*<br />

Listas encadenadas con punteros: con los datos ingresados<br />

formamos dos listas, una agregando adelante y otra ordenada.<br />

*)<br />

El fin de lista se indica con "nil", pero podria ser otro<br />

nodo, y tambien podria haber un nodo al principio sin datos.<br />

listaadelante: se agrega nueva info adelante.<br />

listaordenada: lista ordenada por "id". Recorremos toda la<br />

lista buscando donde ubicar la nueva info, que se inserta<br />

"despues" del elemento que ya estaba si coinciden las<br />

llaves de comparacion, para insertar "antes" cambiar<br />

"


listas (Problema 14.1) Pág. 207<br />

nodo = record<br />

info: info;<br />

siguiente: ptrnodo<br />

end;<br />

var<br />

nuevainfo: info;<br />

listaadelante, listaordenada: ptrnodo;<br />

function entrardatos(var datos: info): boolean;<br />

begin<br />

write(’Entrar el nombre (fin = ): ’);<br />

if (not eoln) then begin<br />

entrardatos := true;<br />

with datos do begin<br />

readln(nombre);<br />

write(’Entrar numero de id para "’, nombre, ’": ’);<br />

readln(id)<br />

end<br />

end<br />

else begin entrardatos := false; readln end<br />

end;<br />

procedure adelante (var lista: ptrnodo; nuevainfo: info);<br />

(*<br />

agregar nueva info adelante en lista,<br />

lista puede ser "nil"<br />

*)<br />

var p: ptrnodo;<br />

begin<br />

new(p);<br />

p^.info := nuevainfo; p^.siguiente := lista;<br />

lista := p<br />

end;<br />

procedure insertaradelante( p, q: ptrnodo);<br />

(* insertar q delante de p, ninguno debe ser "nil" *)<br />

var temp: info;<br />

begin<br />

q^.siguiente := p^.siguiente; p^.siguiente := q;<br />

temp := p^.info; p^.info := q^.info; q^.info := temp<br />

end;<br />

procedure insertardetras( p, q: ptrnodo);<br />

(* insertar q detras de p, ninguno debe ser "nil" *)<br />

begin<br />

q^.siguiente := p^.siguiente; p^.siguiente := q<br />

end;<br />

procedure ordenada (var lista: ptrnodo; nuevainfo: info);


Pág. 208 Programas mencionados<br />

(* agregar elementos a una lista ordenada por id *)<br />

var p, r: ptrnodo;<br />

begin<br />

(* nuevo nodo con nueva info *)<br />

new(r); r^.info := nuevainfo;<br />

(* buscar donde ubicar r *)<br />

if (lista = nil) then begin<br />

(* r es el primer nodo *)<br />

lista := r; r^.siguiente := nil end<br />

else begin (* recorrer la lista *)<br />

p := lista;<br />

while ((p^.siguiente nil) and<br />

(p^.info.id nuevainfo.id)<br />

(* cambiar a >= para antes *)<br />

then insertaradelante(p, r)<br />

else (* p es el ultimo de la lista *)<br />

insertardetras(p, r)<br />

end<br />

end;<br />

procedure imprimir (lista: ptrnodo);<br />

var p: ptrnodo;<br />

begin<br />

if (lista = nil) then writeln(’ Lista vacia’);<br />

p := lista;<br />

while (p nil) do begin<br />

with p^.info do writeln(nombre:tamanio, id:10);<br />

p := p^.siguiente<br />

end<br />

end;<br />

begin<br />

(* carteles *)<br />

writeln(’** Prueba de listas encadenadas’);<br />

writeln;<br />

(* inicializacion de listas *)<br />

listaadelante := nil;<br />

listaordenada := nil;<br />

(* entrada de datos y construccion de listas *)<br />

while (entrardatos(nuevainfo)) do begin<br />

adelante( listaadelante, nuevainfo);<br />

ordenada( listaordenada, nuevainfo)<br />

end;


grafos (Problema 14.9) Pág. 209<br />

(* imprimir listas *)<br />

writeln; writeln(’Las listas son:’); writeln;<br />

writeln(’ Lista agregando adelante:’);<br />

imprimir(listaadelante); writeln;<br />

writeln(’ Lista ordenada por id:’);<br />

imprimir(listaordenada);<br />

(* Fin *)<br />

writeln; writeln(’** Fin **’)<br />

end.<br />

Ejemplo de Salida<br />

** Prueba de listas encadenadas **<br />

Entrar el nombre (fin = ): Geri<br />

Entrar numero de id para "Geri": 2<br />

Entrar el nombre (fin = ): Pablito<br />

Entrar numero de id para "Pablito": 1<br />

Entrar el nombre (fin = ): Guille<br />

Entrar numero de id para "Guille": 2<br />

Entrar el nombre (fin = ): Robin Hood<br />

Entrar numero de id para "Robin Hood": 3<br />

Entrar el nombre (fin = ):<br />

Las listas son:<br />

Lista agregando adelante:<br />

Lista ordenada por id:<br />

** Fin **<br />

Problema 14.9: grafos (pág. 161)<br />

Robin Hood 3<br />

Guille 2<br />

Pablito 1<br />

Geri 2<br />

Pablito 1<br />

Geri 2<br />

Guille 2<br />

Robin Hood 3<br />

program grafos(input, output);<br />

(*<br />

Representaciones de grafos dirigidos donde<br />

V = (1,...,n) por lista de aristas o por<br />

listas de vecinos.


Pág. 210 Programas mencionados<br />

*)<br />

Siempre n es dato.<br />

Las listas terminan en "nil".<br />

type<br />

ptrarista = ^arista;<br />

arista = record u, v: integer; s: ptrarista end;<br />

ptrvecino = ^vecino;<br />

vecino = record id: integer; s: ptrvecino end;<br />

ptrvertice = ^vertice;<br />

vertice = record<br />

id: integer;<br />

s: ptrvertice;<br />

vecinos: ptrvecino<br />

end;<br />

var<br />

ngrafo: integer;<br />

aristas: ptrarista;<br />

vertices: ptrvertice;<br />

(***************************************<br />

Aristas<br />

***************************************)<br />

function nuevaarista(var x, y: integer): boolean;<br />

begin<br />

writeln;<br />

writeln(’Entrar una arista (fin = ): ’);<br />

write(’ Entrar el primer vertice: ’);<br />

if (not eoln) then begin<br />

nuevaarista := true;<br />

read(x);<br />

write(’ Entrar el segundo vertice: ’);<br />

readln(y)<br />

end (* if not eoln *)<br />

else begin nuevaarista := false; readln end<br />

end;<br />

procedure agregararista(x, y: integer);<br />

(* agregamos arista adelante *)<br />

var parista: ptrarista;<br />

begin<br />

new(parista);<br />

with parista^ do begin u := x; v := y; s := aristas end;<br />

aristas := parista


grafos (Problema 14.9) Pág. 211<br />

end;<br />

procedure entrararistas;<br />

(* inicializar e ingresar aristas *)<br />

var x, y: integer;<br />

begin<br />

aristas := nil;<br />

while nuevaarista(x,y) do agregararista(x,y)<br />

end;<br />

procedure escribiraristas;<br />

const maxrenglon = 10; (* maxima cantidad por renglon *)<br />

var<br />

k: integer;<br />

p: ptrarista;<br />

begin<br />

if (aristas = nil) then writeln(’** lista vacia’)<br />

else begin<br />

p := aristas; k := 0;<br />

repeat<br />

k := k + 1;<br />

with p^ do write(’ (’, u:1, ’,’, v:1, ’)’);<br />

if ((k mod maxrenglon) = 0) then writeln;<br />

p := p^.s<br />

until (p = nil)<br />

end;<br />

if ((k mod maxrenglon) > 0) then writeln<br />

end;<br />

(***************************************<br />

Vertices y sus vecinos<br />

***************************************)<br />

procedure inicializarvecinos;<br />

var<br />

k: integer;<br />

p: ptrvertice;<br />

begin<br />

vertices := nil;<br />

(* la construimos "al reves" para que quede ordenada *)<br />

for k := ngrafo downto 1 do begin<br />

new(p);<br />

with p^ do begin<br />

id := k; s := vertices; vecinos := nil end;<br />

vertices := p<br />

end (* for k *)<br />

end;<br />

procedure agregarvecino(x, y: integer);<br />

(* agregar y a los vecinos de x *)


Pág. 212 Programas mencionados<br />

var<br />

pvertice: ptrvertice;<br />

pvecino: ptrvecino;<br />

begin<br />

(* buscar x entre los vertices *)<br />

pvertice := vertices; (* aca debe ser vertices nil *)<br />

while (pvertice^.id x) do pvertice := pvertice^.s;<br />

(* actualizar vecinos de "x", agregando "y" adelante *)<br />

new(pvecino);<br />

with pvecino^ do begin s := pvertice^.vecinos; id := y end;<br />

pvertice^.vecinos := pvecino<br />

end;<br />

procedure escribirvecinos;<br />

var<br />

pvertice: ptrvertice;<br />

pvecino: ptrvecino;<br />

begin<br />

(* adyacencias *)<br />

pvertice := vertices;<br />

while (pvertice nil) do begin<br />

write(pvertice^.id:5, ’ -> ’);<br />

pvecino := pvertice^.vecinos;<br />

if (pvecino = nil) then write(’ {}’)<br />

else<br />

repeat<br />

write(pvecino^.id:3);<br />

pvecino := pvecino^.s<br />

until (pvecino = nil);<br />

writeln;<br />

pvertice := pvertice^.s<br />

end<br />

end;<br />

(***************************************<br />

De aristas a vertices<br />

***************************************)<br />

procedure dearistasaadyacencias;<br />

(* construir listas de adyacencias<br />

recorriendo la lista de aristas *)<br />

var parista: ptrarista;<br />

begin<br />

parista := aristas;<br />

while parista nil do begin<br />

with parista^ do begin<br />

agregarvecino(u, v);<br />

agregarvecino(v, u)<br />

end;


grafos (Problema 14.9) Pág. 213<br />

end;<br />

(* proxima arista *)<br />

parista := parista^.s<br />

end<br />

(**************************************<br />

Parte principal<br />

**************************************)<br />

begin<br />

(* carteles *)<br />

writeln(’** Pasar de aristas a adyacencias usando’);<br />

writeln(’ listas encadenadas.’); writeln;<br />

(* Inicializacion y entrada de datos *)<br />

(* vertices *)<br />

write(’ Entrar n (la cantidad de vertices): ’);<br />

readln(ngrafo);<br />

inicializarvecinos; writeln;<br />

(* aristas *)<br />

entrararistas;<br />

(* construccion de listas de adyacencias *)<br />

dearistasaadyacencias;<br />

(* imprimir resultados *)<br />

writeln; writeln(’** Resultados **’); writeln;<br />

writeln(’ Lista de aristas:’);<br />

escribiraristas; writeln;<br />

writeln(’ Listas de vecinos:’);<br />

escribirvecinos;<br />

(* fin *)<br />

writeln; writeln(’** Fin **’)<br />

end.


Apéndice B<br />

Sobre los compiladores<br />

Pascal<br />

Suponiendo que se tenga acceso a una computadora, el próximo problema es<br />

usar un compilador Pascal. Si bien los compiladores comerciales tienen menos<br />

problemas y tienen documentación adecuada, afortunadamente hay varias posibilidades<br />

de compiladores disponibles gratis de internet, y pasamos a presentar<br />

algunas (1) :<br />

• Aunque no se adhiere estrictamente al estándar Pascal, el compilador<br />

“Turbo Pascal” de Borland (para DOS) es muy “robusto” (no tiene problemas),<br />

y su popularidad ha hecho que muchos otros compiladores copiaran<br />

su sintaxis.<br />

La versión 5.5 de este compilador se puede conseguir gratis para uso personal<br />

en Borland Museum (2) . Versiones más recientes de Turbo Pascal o<br />

Delphi (como Turbo Pascal pero para Windows), también para uso personal<br />

pero en francés, pueden conseguirse en Borland Francia (3) .<br />

• Free Pascal (4) , es un compilador gratuito para varias plataformas, incluyendo<br />

DOS, Win32 y Linux, entre otras. La página principal de Free Pascal<br />

(5) tiene más datos sobre Free Pascal y Pascal en general.<br />

• Thefreecountry.com (6) presenta varias posibilidades de compiladores gratis,<br />

incluyendo Turbo Pascal y Delphi.<br />

• Para Macintosh, puede consultarse la página de Pascal Central (7) para<br />

distintas posibilidades, incluyendo programas hechos.<br />

Claro que una vez obtenido el compilador, debe instalarse en la computadora<br />

siguiendo las instrucciones para cada caso.<br />

(1) La información está actualizada a febrero de <strong>2004</strong>.<br />

(2) http://community.borland.com/museum/<br />

(3) http://www.inprise.fr/download/compilateurs/<br />

(4) http://www.freepascal.org/download.html<br />

(5) http://www.freepascal.org/<br />

(6) http://www.thefreecountry.com/developercity/pascal.shtml<br />

(7) http://www.pascal-central.com/


Apéndice C<br />

Breve referencia de Pascal<br />

C.1. Resumen de operadores<br />

C.1.1. Operadores aritméticos<br />

Operador operación operando resultado<br />

+ (unitario) identidad entero o real mismo que operando<br />

- (unitario) inversión de signo entero o real mismo que operando<br />

+ (binario) suma entero o real entero o real<br />

- (binario) resta entero o real entero o real<br />

* producto entero o real entero o real<br />

div división entera entero entero<br />

/ división entero o real real<br />

mod resto entero entero<br />

C.1.2. Operadores relacionales<br />

Operador operación operando resultado<br />

= igualdad simple, string o puntero lógico<br />

desigualdad simple, string o puntero lógico<br />

< menor simple o string lógico<br />

> mayor simple o string lógico<br />

= mayor o igual simple o string lógico<br />

C.1.3. Operadores lógicos<br />

Operador operación operando resultado<br />

not negación lógico lógico<br />

or disyunción lógico lógico<br />

and conjunción lógico lógico<br />

C.2. Identificadores estándares<br />

Constantes


Pág. 216 Breve referencia de Pascal<br />

Tipos<br />

Variables<br />

false maxint true<br />

boolean char integer real text<br />

input output<br />

Funciones con argumentos numéricos<br />

Función operación argumento resultado<br />

abs valor absoluto entero o real entero o real<br />

arctan arco tangente entero o real real<br />

chr carácter en orden entero carácter<br />

exp exponencial (e x ) entero o real real<br />

ln logaritmo (base e) entero o real real<br />

odd (∗) si impar entero lógico<br />

round (†) redondeo real entero<br />

sin seno real o entero real<br />

sqr cuadrado real o entero real o entero<br />

sqrt raíz cuadrada real o entero (≥ 0) real<br />

trunc (‡) truncar real entero<br />

(∗) odd (x) es verdadero si x es impar.<br />

(†)<br />

round (x) =trunc(x +0.5) para x ≥ 0, y round (x) =trunc(x − 0.5) para<br />

x ≤ 0.<br />

Otras funciones<br />

(‡) trunc(x) = ⌊x⌋ si x ≥ 0,<br />

⌈x⌉ si x


C.3. Nombres Reservados Pág. 217<br />

C.3. Nombres Reservados<br />

and array begin case const div<br />

downto do else end file for<br />

function goto if in label mod<br />

nil not of or packed procedure<br />

program record repeat set then to<br />

type until var while with


Apéndice D<br />

Algunas notaciones y<br />

símbolos usados<br />

Ponemos aquí algunas notaciones, abreviaturas y expresiones usadas (que<br />

pueden diferir de algunas ya conocidas), sólo como referencia: el lector debería<br />

mirarlo rápidamente y volver cuando surja alguna duda.<br />

D.1. Lógica<br />

⇒ implica o entonces. x > 0 ⇒ x = √ x 2 puede leerse como “si x es<br />

positivo, entonces. . . ”.<br />

⇔ si y sólo si. Significa que las condiciones a ambos lados son equivalentes.<br />

Por ejemplo x ≥ 0 ⇔|x| = x se lee “x es positivo si y sólo si. . . ”.<br />

∃ existe. ∃ k ∈ Z tal que. . . se lee “existe k entero tal que...”.<br />

∀ para todo. ∀ x>0, x = √ x 2 se lee “para todo x positivo,...”.<br />

¬ La negación lógica no. Sip es una proposición lógica, ¬p se lee “no p”.<br />

¬p es verdadera ⇔ p es falsa.<br />

∧ La conjunción lógica y. Sip y q son proposiciones lógicas, p ∧ q es<br />

verdadera ⇔ tanto p como q son verdaderas.<br />

∨ La disyunción lógica o. Sip y q son proposiciones lógicas, p ∨ q es<br />

verdadera ⇔ obienp es verdadera o bien q es verdadera.<br />

D.2. Conjuntos<br />

∈ pertenece. x ∈ A significa que x es un elemento de A.<br />

/∈ no pertenece. x ∈ A significa que x no es un elemento de A.<br />

∪ unión de conjuntos, A ∪ B = {x : x ∈ A o x ∈ B}.<br />

∩ intersección de conjuntos, A ∩ B = {x : x ∈ A y x ∈ B}.


D.3. Números: conjuntos, relaciones, funciones Pág. 219<br />

\ diferencia de conjuntos, A \ B = {x : x ∈ A y x/∈ B}.<br />

||,<br />

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

A.<br />

∅ El conjunto vacío, #(∅) =0.<br />

D.3. Números: conjuntos, relaciones, funciones<br />

N El conjunto de números naturales, N = {1, 2, 3,...}. Observar que para<br />

nosotros 0 /∈ N.<br />

Z Los enteros, Z = {0, 1, −1, 2, −2,...}.<br />

Q Los racionales p/q, donde p, q ∈ Z, q = 0.<br />

R Los reales. Son todos los racionales más números como √ 2, π, etc.,que<br />

no tienen una expresión decimal periódica.<br />

R+<br />

Los reales positivos, R+ = {x ∈ R : x>0}.<br />

C Los complejos, de la forma z = a + bi, cona, b ∈ R y donde i es la<br />

unidad imaginaria, “ i = √ −1”.<br />

±x Dado el número x, ±x representa dos números: x y −x.<br />

| Para m, n ∈ Z, m | n se lee m divide a n, on es múltiplo de m,ysignifica<br />

que existe k ∈ Z tal que n = km.<br />

≈ aproximadamente. x ≈ y se lee “x es aproximadamente igual a y”.<br />

≪ mucho menor. x ≪ y se lee “x es mucho menor que y”.<br />

≫ mucho mayor. x ≫ y se lee “x es mucho mayor que y”.<br />

|x| El valor<br />

<br />

absoluto o módulo del número x. Siz = a+bi∈ C con a, b ∈ R,<br />

|z| = a2 + b2 .<br />

⌊x⌋ El piso de x, x ∈ R. Es el mayor entero que no supera a x, porloque<br />

⌊x⌋ ≤x0, o logaritmo en base e, lnx =<br />

log e x. Es la inversa de la exponencial, y =lnx ⇔ e y = x.


Pág. 220 Algunas notaciones y símbolos usados<br />

✎ Para no confundir con los logaritmos en base 10, evitaremos la<br />

notación log x: si nos referimos a la base e usaremos ln x, yen<br />

otras bases log b x.<br />

sen x,<br />

sin x Las función trigonométrica seno, definida para x ∈ R. Su inversa es la<br />

función arcsen (arcsin en inglés).<br />

cos x Las función trigonométrica coseno, definida para x ∈ R. Su inversa es<br />

la función arccos.<br />

tan x Las función trigonométrica tangente, definida como tan x =senx/ cos x.<br />

Su inversa es la función arctan.<br />

arcsen x,<br />

arccos x,<br />

arctan x Funciones trigonométricas inversas respectivamente de sen, cos y tan.<br />

sgn(x),<br />

signo(x) Las función signo, definida para x ∈ R por<br />

⎧<br />

⎪⎨ 1 si x>0,<br />

signo(x) = 0 si x =0,<br />

⎪⎩<br />

−1 si x


Bibliografía<br />

[1] E. M. Baras: Lotus 1-2-3 — Guía del Usuario (2. a ed.), Osborne - Mc-<br />

Graw Hill, 1990. (Citado en pág. 171.)<br />

[2] J. L. Bentley: More Programming Pearls, Addison-Wesley, 1988. (Citado<br />

en pág. 165.)<br />

[3] N. L. Biggs: Introduction to Computing with Pascal, Oxford Science Publications,<br />

1989. (Citado en pág. 24.)<br />

[4] A. Engel: Exploring Mathematics with your computer, The Mathematical<br />

Association of America, 1993. (Citado en págs. 2, 5, 65, 90 y 171.)<br />

[5] E. Gentile: Aritmética elemental en la formación matemática, Red<br />

Olímpica, 1991. (Citado en pág. 5.)<br />

[6] G. H. Hardy y E. M. Wright: An Introduction to the Theory of Numbers<br />

(4. a ed.), Oxford University Press, 1975. (Citado en pág. 171.)<br />

[7] K. Jensen y N. Wirth: Pascal—User Manual and Report, Springer Verlag,<br />

1985. (Citado en págs. 2, 5, 90 y 109.)<br />

[8] R. Johnsonbaugh: Matemáticas Discretas (4. a ed.), Prentice Hall, 1999.<br />

(Citado en pág. 140.)<br />

[9] SirT.L.Heath: The thirteen books of Euclid’s Elements, vol.2,Dover<br />

Publications, 1956. (Citado en pág. 54.)<br />

[10] B. W. Kernighan y D. M. Ritchie: El lenguaje de programación C<br />

(2. a ed.), Prentice-Hall Hispanoamericana, 1991. (Citado en págs. 5, 12,<br />

132 y 162.)<br />

[11] D. E. Knuth: The Art of Computer Programming, Addison-Wesley. Vol. 1<br />

(3. a ed.), 1997; vol. 2 (3. a ed.), 1997; vol. 3 (2. a ed.), 1997. (Citado en págs.<br />

92, 96 y 165.)<br />

[12] C. H. Papadimitriou y K. Steiglitz: Combinatorial Optimization, Algorithms<br />

and Complexity, Dover, 1998. (Citado en pág. 154.)<br />

[13] K. H. Rosen: Elementary Number Theory and its Applications (3. a ed.),<br />

Addison Wesley, 1993. (Citado en pág. 39.)<br />

[14] H. A. Taha: Operations Research — An Introduction (4. a ed.), MacMillan<br />

Publishing Co., 1987. (Citado en pág. 171.)


Pág. 222 Bibliografía<br />

[15] N. Wirth: Introducción a la Programación Sistemática, El Ateneo, 1984.<br />

(Citado en págs. 2 y 80.)<br />

[16] N. Wirth: Algoritmos y Estructuras de Datos, Prentice-Hall Hispanoamericana,<br />

1987. (Citado en págs. 2, 5, 80, 96, 102, 109, 131, 132 y 162.)


Índice de figuras<br />

2.1. Esquema de transferencia de datos en la computadora. ...... 8<br />

2.2. Bits y byte. .............................. 8<br />

2.3. Esquema del desarrollo de un programa. . . . . . . . . . . . . . . 9<br />

2.4. Esquema del programa ejecutable en la memoria. . . . . . . . . . 10<br />

3.1. Datos de tipos elementales en la memoria. ............. 14<br />

3.2. Los datos con sus identificadores. .................. 14<br />

3.3. Esquema de la “densidad variable”. . . . . . . . . . . . . . . . . . 20<br />

5.1. Gráficos de y =cosx y y = x. .................... 44<br />

5.2. Aproximándose al punto fijo de cos x. ............... 44<br />

6.1. Esquema del arreglo v guardado en memoria. . . . . . . . . . . . 56<br />

6.2. Esquema del problema de Flavio Josefo con n =5ym =3. . . . 62<br />

7.1. Una función continua con distintos signos en los extremos. .... 71<br />

7.2. Programa, funciones y procedimientos en la memoria. ...... 75<br />

7.3. Intercambio de los valores de u y v. . . . . . . . . . . . . . . . . . 77<br />

10.1. Ordenando por conteo. ........................103<br />

10.2. Esquema del registro de tipo “ complejo” enmemoria.......105<br />

11.1. Contando la cantidad de caminos posibles. .............112<br />

11.2. Las torres de Hanoi. . . . . . . . . . . . . . . . . . . . . . . . . . 114<br />

12.1. Una estructura “lineal”. . . . . . . . . . . . . . . . . . . . . . . . 126<br />

12.2. Un árbol binario ordenado. . . . . . . . . . . . . . . . . . . . . . 127<br />

12.3. Disposición del arreglo de registros luego de ingresar los datos. . 128<br />

12.4. Disposición del árbol binario luego de ingresar los datos. . . . . . 128<br />

12.5. Los registros de la figura 12.4 “proyectados”. . . . . . . . . . . . 128<br />

12.6. Los registros con índices para la estructura de árbol binario. . . . 129<br />

13.1. Un grafo con 6 vértices y 7 aristas. . . . . . . . . . . . . . . . . . 134<br />

13.2. Un grafo no conexo y un árbol. . . . . . . . . . . . . . . . . . . . 135<br />

13.3. Un grafo con pesos en las aristas. ..................142<br />

14.1. El puntero p y la variable apuntada pˆ. . . . . . . . . . . . . . . 156<br />

15.1. Aproximación de sen x mediante un polinomio. . . . . . . . . . . 163


Índice de cuadros<br />

8.1. Estructura de un programa Pascal. . . . . . . . . . . . . . . . . . 82<br />

8.2. Diferencias entre el estándar y Turbo Pascal. ........... 88<br />

10.1. Comparación de algoritmos de clasificación. . . . . . . . . . . . . 103<br />

13.1. Esquema del algoritmo visitar. . . . . . . . . . . . . . . . . . . . 138<br />

13.2. Esquema del algoritmo de Dijkstra. . . . . . . . . . . . . . . . . . 143<br />

13.3. Esquema del algoritmo de Kruskal. . . . . . . . . . . . . . . . . . 150


Índice alfabético<br />

εmaq, 34, 220<br />

εmin, 19, 34, 220<br />

φ (de Euler), 53<br />

adyacencia<br />

lista de, 161<br />

matriz de, 136<br />

adyacente (vértice), 134<br />

aguja<br />

de Buffon, 94<br />

Hanoi, 113, 121<br />

Al-Khwarizmi, 25<br />

algoritmo, 25<br />

de búsqueda, ver búsqueda<br />

de clasificación, ver clasificación<br />

de Eratóstenes, 60<br />

de Euclides, 49<br />

de Prim, 145<br />

Dijkstra, 141<br />

para grafos, ver grafo<br />

para permutaciones, 124<br />

para subconjuntos, 121<br />

visitar, 137<br />

and, 23<br />

aproximación, ver también convergencia,<br />

método de Monte<br />

Carlo<br />

acantidaddeprimos,61<br />

de n!, 111<br />

de sen x, 164<br />

de número armónico Hn, 41<br />

de raíz cuadrada, 45<br />

de raíz de ecuación, 70<br />

árbol<br />

binario ordenado, 126<br />

caracterización, 135<br />

definición, 135<br />

hoja, 135<br />

rama, 135<br />

archivo<br />

caja de herramientas, 84<br />

de texto, 86, ver también editor<br />

de textos<br />

para guardar programa, 10<br />

arista<br />

de grafo, 133<br />

extremo, 134<br />

lista de, 136, 161<br />

Arquímedes, 59<br />

array, ver arreglo<br />

arreglo, 55, ver también lista<br />

como conjunto, 104<br />

dimensión, 55<br />

ASCII (código), 23, 24<br />

año bisiesto, 27<br />

begin-end<br />

en parte principal, 10<br />

para agrupar, 28<br />

para funciones y procedimientos,<br />

67<br />

Bigollo, ver Fibonacci<br />

Binet, 116<br />

bisección (método), 70<br />

bit, 7<br />

Boole, 13<br />

boolean, 13<br />

bucle (lazo), 30<br />

Buffon, 94<br />

búsqueda<br />

aloancho,139<br />

binaria, 98, 169<br />

en grafo, 137<br />

en profundidad, 140<br />

exhaustiva, 132<br />

lineal, 57<br />

lineal con centinela, 96<br />

byte, 8<br />

caja de herramientas, 84<br />

cálculo


Pág. 226 Índice alfabético<br />

de suma o producto, 32<br />

numérico, ver sec. 5.1<br />

calendario (juliano y gregoriano), 27<br />

camino en grafo, 134<br />

carácter<br />

comparación, 27<br />

imprimible, 23<br />

yordinal,24<br />

case, 26<br />

caótico, 47<br />

char, 13, ver también carácter<br />

chr, 24<br />

ciclo<br />

de Euler, 162<br />

en grafo, 134<br />

clasificación<br />

comparación de métodos, 103<br />

de caracteres, 27<br />

de registros, 107, 108<br />

estabilidad, 109<br />

inserción directa, 101<br />

intercambio directo, 101<br />

lista encadenada, 159, 160<br />

métodos, 101<br />

por conteo, 103<br />

por fusión, 102, 166<br />

recursiva, 112<br />

selección directa, 101<br />

yárbol binario, 126<br />

cola, 119<br />

de prioridad, 145<br />

del supermercado, 168<br />

fifo, 119, 140<br />

lifo (pila), 119, 140<br />

consola, 7<br />

const, 32, 57, 66<br />

contador, 31, 32, 34, 93, 122–124,<br />

126<br />

en arreglo, 56<br />

en lazo, 71, 97, 102, 104<br />

en “ for ”, ver variable de control<br />

convergencia, 42, 43, ver también sec.<br />

5.1<br />

cuadrática, 54<br />

de n/ ln n, 61<br />

de polinomios, 63<br />

de punto fijo, 47<br />

global, 54<br />

método de punto fijo, 42<br />

CPU, 7<br />

criba, 59<br />

de Eratóstenes, 59<br />

criterio de parada, 47, 51, 70, 72<br />

de Moivre, A., 116<br />

De Morgan, 63<br />

leyes de, 23<br />

densidad, 19<br />

Dijkstra, 142, 146<br />

algoritmo, 141<br />

Diofanto, 49<br />

Dirichlet, 43, 65<br />

principio de, 94<br />

dispose, 156<br />

div, 17<br />

do<br />

y for, 35<br />

y while, 30<br />

downto, 36, ver también for-do<br />

e (= 2.718 ...), 36, 42, 111, 219<br />

Monte Carlo, 164<br />

ecuación<br />

cuadrática, 41<br />

diofántica, 48<br />

no lineal, 49<br />

editor de textos, 9, 88, 89, 161, ver<br />

también archivo de texto<br />

else, ver if-then<br />

end, ver también begin-end<br />

“ ; ”antesde,29<br />

sin begin, 105<br />

eof, 88<br />

eoln, 37<br />

epsilon<br />

de máquina (εmaq), 34<br />

mínimo (εmin), 34<br />

Eratóstenes, 59<br />

criba, 59<br />

error<br />

absoluto y relativo, 47<br />

numérico, 33, 34, 36, 41, 45, 72<br />

estrategia, ver método<br />

Euclides, 50, 59<br />

algoritmo para mcd, 48–50<br />

Euler, 54, 116, 133<br />

ciclo de, 141, 162<br />

constante de (γ), 41<br />

función φ, 53


Índice alfabético Pág. 227<br />

factorial, 33<br />

recursivo, 111<br />

<strong>Fe</strong>rmat, 49, 65<br />

Fibonacci, 116<br />

número de, 115, 123, 168<br />

Flavio Josefo, 61, 160<br />

for-do, 35<br />

fórmula<br />

de Gauss, 33, 48, 110<br />

de Stirling para n!, 111<br />

Euler-Binet, 116<br />

para x y , 36<br />

forward, 74<br />

función continua, 54<br />

function, ver cap. 7<br />

Gauss, 33<br />

suma de, 32, 41, 110<br />

get (en Pascal), 120<br />

get (en cola), 120<br />

Goldbach, 54<br />

cuenta de, 164<br />

grafo, 133, 137<br />

arista, 133<br />

conexo, 134<br />

dirigido (digrafo), 135<br />

representación, 136<br />

simple, 134<br />

vértice, 133<br />

y lista encadenada, 161<br />

Gregorio (Papa), 27<br />

Hanoi (torres de), 113<br />

Horner, 63<br />

regla de, 63<br />

identificador (de variable), 13<br />

if-then, 26<br />

input al comienzo de programa, 10<br />

integer, 13<br />

juego, ver también problema<br />

cartas (clasificar), 101<br />

de Nim, 169<br />

generala, 164<br />

Julio César, 27<br />

Kruskal, 146<br />

algoritmo, 149<br />

Lagrange, 163, 164<br />

lazo, 30<br />

Leonardo de Pisa, ver Fibonacci<br />

lineal<br />

estructura de arreglo, 126<br />

lista, 160<br />

lista, ver también arreglo<br />

de aristas, 136<br />

encadenada, 157<br />

matriz<br />

arreglo multidimensional, 85<br />

de adyacencias, 136<br />

de costos, 144<br />

máximo común divisor, ver mcd<br />

maxint, 19, 220, ver también integer<br />

mcd, 48, 49, 53, 65, 164<br />

recursivo, 116<br />

media (indicador estadístico), 168<br />

mediana (indicador estadístico), 168<br />

memoria (de computadora), 7<br />

método<br />

babilónico, 45, 53<br />

de barrido, 48<br />

de bisección, 70<br />

de clasificación ver clasificación,<br />

101<br />

de Monte Carlo<br />

para e, 164<br />

para π, 93, 94<br />

de Newton-Raphson, 46<br />

de punto fijo, 42<br />

mínimo común múltiplo (mcm), 52<br />

mod, 22<br />

moda (indicador estadístico), 168<br />

new, 156<br />

Newton, 46<br />

nil, 156<br />

nivel (en árbol), 135<br />

normalización, 47<br />

not, 23<br />

número<br />

armónico (Hn), 41<br />

de Fibonacci, ver Fibonacci<br />

entero, ver integer<br />

primo, ver primo<br />

real, ver real<br />

or, 23


Pág. 228 Índice alfabético<br />

ord, 24<br />

ordinal y carácter, 24<br />

output al comienzo de programa, 10<br />

packed, 83<br />

parámetro<br />

formal, 77<br />

real, 77<br />

pasar (por valor o referencia), 77<br />

Pascal (Blaise), 9<br />

período (en expresión decimal), 64,<br />

89<br />

pi (π), 32, 164<br />

en programa, 32, 76<br />

Monte Carlo, 93, 94<br />

punto fijo, 45<br />

pila, 119<br />

Pitágoras, 50<br />

terna pitagórica, 49<br />

polinomio, 62<br />

de Lagrange, 63, 163<br />

pop (en pila), 120<br />

potencia (cálculo de), 36, 65–68<br />

Prim, 146<br />

algoritmo, 145<br />

primo, 52, 54, 55, 65, 113, ver también<br />

criba de Eratóstenes<br />

Euclides, 52<br />

existencia de infinitos, 52<br />

factorización, 50, 52<br />

y recursión, 117<br />

primos entre sí (coprimos),50<br />

principio<br />

de Dirichlet, 94<br />

del casillero, 94<br />

del palomar, 94<br />

problema<br />

cola del supermercado, 168<br />

contando palabras en texto, 89<br />

cuenta de Goldbach, 164<br />

de “manos”, 165<br />

de Flavio Josefo, 61, 160<br />

de las cajas, 100, 108<br />

del cumpleaños, 94<br />

del viajante, 131<br />

interés sobre saldo, 73<br />

numérico, 41<br />

producción de fábrica, 169<br />

show de televisión, 94<br />

torres de Hanoi, 113, 117<br />

visibilidad en el plano, 164<br />

procedure, ver cap. 7<br />

programa, 8<br />

ejecutable, 9<br />

fuente, 9<br />

mencionado<br />

anchoprimero, 139–141, 144,<br />

145, 147, 148, 153, 162, 199<br />

arbolbinario, 129, 140, 156, 157,<br />

160, 197<br />

babilonico, 47, 181<br />

biseccion, 71, 72, 189<br />

busquedalineal, 57, 59, 82, 96,<br />

184<br />

caracteres1, 24, 175<br />

caracteres2, 27, 176<br />

cifras, 34, 179<br />

comparar, 27, 176<br />

dado, 92, 195<br />

dados, 92, 93, 196<br />

dearchivoaconsola, 87, 89, 195<br />

deconsolaaarchivo, 194<br />

deconsolaarchivo, 87–89<br />

dijkstra, 144, 145, 147, 148,<br />

151–153, 202<br />

enteroareal, 20, 174<br />

eolnprueba, 38, 180<br />

epsmin, 34, 37, 179<br />

eratostenes, 60, 186<br />

euclides, 51, 182<br />

factoresprimos, 53, 183<br />

flaviojosefo1, 61, 187<br />

gauss, 33, 36, 37, 178<br />

grafos, 161, 209<br />

holamundo, 10, 173<br />

intercambio, 77, 192<br />

leerdato, 17, 26, 173<br />

listas, 159, 160, 206<br />

maximocaracter, 83, 84, 86, 121,<br />

192<br />

positivo, 22, 23, 175<br />

potencia, 36, 180<br />

potencias2, 67, 68, 188<br />

potencias3, 68, 77, 188<br />

raiz, 18, 174<br />

resto, 30, 37, 62, 177<br />

segundos, 22, 175<br />

sumardatos, 39, 56, 57, 181<br />

sumardos, 16, 17, 21, 173<br />

tablaseno, 32, 37, 75, 76, 177


Índice alfabético Pág. 229<br />

tablaseno2, 76, 77, 191<br />

unidades, 56, 58, 184<br />

valorabsoluto, 26, 176<br />

prueba de escritorio, 30<br />

puntero, 155<br />

push (en pila), 120<br />

put (en Pascal), 120<br />

put (en cola), 120<br />

Raphson, 46<br />

read, 17, 83<br />

readln, 17<br />

real, 13<br />

record, 105<br />

recorrido de grafo, 137<br />

registro, 105<br />

repeat-until, 34<br />

reset, 87<br />

rewrite, 87<br />

signo (función), 43, 220<br />

Stirling, 111<br />

suma de Gauss, ver Gauss<br />

técnica, ver método<br />

teorema<br />

caracterización de árboles, 135,<br />

146<br />

de Dirichlet, 65<br />

de Euler, 133, 141<br />

de los números primos, 61<br />

suma de grados, 137<br />

terna pitagórica, 49<br />

text<br />

como argumento, 87<br />

tipo, 68, 86<br />

texto, ver editor de, archivo de<br />

then, ver if-then<br />

tipo (de variable), 13<br />

to, ver for-do<br />

type, 81<br />

until, ver repeat-until<br />

Van der Monde, 163<br />

var<br />

declaración de variable, 14<br />

pasar por referencia, 78<br />

variable, 13<br />

carácter (char), 13, 23<br />

de control en “ for ”, 36<br />

elemental, 13<br />

entera (integer), 13, 16<br />

global, 69<br />

local, 68<br />

lógica (boolean), 13, 22<br />

real (real), 13, 16<br />

vértice<br />

adyacente, 134, 161<br />

aislado, 134<br />

de grafo, 133<br />

grado, 137<br />

von Neumann, 7<br />

while-do, 30<br />

Wiles, 49, 65<br />

with, 106<br />

write, 16<br />

writeln, 11<br />

Zeckendof, 168

Hooray! Your file is uploaded and ready to be published.

Saved successfully!

Ooh no, something went wrong!