Beruflich Dokumente
Kultur Dokumente
Justo Nº 4287
(0223) 472-2408
Mar del Plata
ABDALA - MARMOL
Unidad Nº1 Algoritmos
Contenidos:
Contenidos
Comentarios 6
Sentencia Program 6
Declaración de Unidades 7
Programa Principal 7
Compilación y Ejecución en memoria 7
Contenidos.
Tipos de datos 8
Variables y Constantes. Declaraciones 8
Tipos de datos 9
o Tipos enteros 9
o Tipos reales 10
o Tipos carácter 10
o Tipos Lógicos 10
Operaciones Básicas
Asignación o igualación 11
Trabajo Práctico Nº1 83
Contenidos.
Sentencias compuestas 12
Ciclo FOR 12
Ciclo While 13
Ciclo REPEAT-UNTIL 13
Bucles controlados por condición: 14
o Continuación del bucle 14
o Valor centinela 14
o Uso de banderas o interruptores 14
Sentencia IF...THEN...ELSE 15
Selecciones CASE 16
Trabajo Práctico estructuras de control 82
Contenidos.
Definición de Procedimiento 17
Creación de los procedimientos 17
Uso de los procedimientos 17
Variables locales y globales 17
Parámetros formales y reales 18
Mecanismos de paso de parámetros 19
Definición de funciones 20
Uso de las funciones 22
Sentencias de Pascal 22
o Halt 22
o INC y DEC 22
Funciones 22
o Truncamiento y redondeo. 22
o Exponenciales y logarítmicas. 22
Contenidos.
Contenidos.
Recursión, concepto 32
La sucesión de Fibonacci 34
Torres de Hanoi 35
Trabajo Práctico Recursividad. 90
3
Multilista 59
67
Unidad Nº 12 Estructuras de datos no lineales. Árboles binarios 69
70
Estructuras de datos no lineales. Árboles binarios 72
Árboles binarios. Recorrido 73
Implementaciones del Árbol binario
Recorrido de un Árbol binario 75
Árboles binarios de búsqueda
Búsqueda de un elemento
Eliminación de un elemento
79
80
Unidad Nº 13 Unidad CRT 81
82
Colores , CheckBreak , DirectVideo, WindMin, WindMax, ClrEol
ClrEol; ClrScr, Delay, DelLine
GotoXY, InsLine, NoSound, TextBackground, TextColor, TextMode
Window, KeyPressed, ReadKey, WhereX WhereY
Unidad Nº 14 Archivos
Archivos de texto 83
Reset, Assign, ReWrite, Close
97
Ejercicios Olimpiadas Algoritmos
Conceptos iniciales:
Resulta necesario dar una definición adecuada de los principales términos implicados en el
planteo de esos objetivos:
Computadora: Es un aparato electrónico capaz de ser programado para ejecutar a gran velocidad
un algoritmo dado.
Algoritmo: Es el conjunto de pasos lógicamente ordenados, tal que partiendo de ciertos datos o
estados iniciales, permite obtener ciertos resultados o estados finales.
En todos los casos, para que un algoritmo pueda ser transmitido, la persona que lo plantea
debe tener una forma o código que sea entendido de alguna manera para quien ejecute el algorit-
mo.
Para que una computadora pueda ser utilizada para resolver un problema dado, los pasos a
seguir se ven en el siguiente esquema:
Cargar el
programa el
Entender
Escribir el la Ejecutar el
el
Plantear el Programa computadora
problema programa
algoritmo
Figura 1
Introducción a la programación:
La información de entrada será procesada por el programa y luego serán procesados para
obtener una información o resultado.
Algoritmo
Entrada
------- Resultado
de datos
Programa
5
Unidad Nº 2 – Estructura de los programas
El lenguaje utilizado en Turbo Pascal es estructurado, lo que significa que cada programa
requiere una forma específica de escritura para que sea entendido por el compilador.
Todo programa cuenta con algunas partes o módulos los cuales son:
Cabecera
Declaraciones
Programa
Cabecera
Todos los programas tienen la misma estructura. Para indicar al compilador que se trata de
un programa tenemos que escribir la palabra program seguida de un espacio y de un nombre (que
puede tener letras y n˙meros) y que tiene que terminar en un punto y coma. Por ejemplo :
program EjemploNumero6;
Es importante indicar que el nombre que demos al programa tiene que seguir unas reglas ya que
sino en caso contrario no será válido para el compilador:
El primer carácter tiene que ser una letra y no una cifra ya que en caso contrario el compilador se
pensarÌa que es un numero. Por ejemplo, no es valido el nombre siguiente:
program 6Ejemplo;
El único carácter no alfanumérico válido es el guión bajo. No se admiten caracteres como exclama-
ciones o signos de interrogación, etc. Tampoco se admiten vocales acentuadas o letras que no
pertenezcan al alfabeto inglés. Por ejemplo la Á y la Ò no se admiten. Finalmente, el nombre del
programa no tiene que coincidir con otras variables o constantes del programa.
Utilización de units
En algunos casos nos puede interesar emplear diferentes funciones y procedimientos que están
definidos en otros archivos. Esta especie de almacenes de rutinas se llaman units y hay que explici-
tarlas cuando queremos emplear alguna de sus rutinas en nuestro programa.
Programa principal
Después de haber realizado todas las declaraciones se puede iniciar con el programa prin-
cipal. (Es posible, antes del programa, declarar las funciones y procedimientos, pero eso se anali-
zará posteriormente).
El programa principal inicia con la palabra reservada BEGIN (inicio) y termina con la pa-
labra END. (fin), esta última con un punto al final.
Cada línea de código, enunciado, sentencia o instrucción completa que se escriba deberá
terminar con un punto y coma “;”.
Solo se omitirá el punto y coma cuando se utiliza la palabra reservada ELSE. Aunque puede
también omitirse si la siguiente expresión es END o UNTIL.
PROGRAM Primera_Prueba;
VAR
Entero : Integer;
CONST
Mensaje = ‘Introduce un valor entero: ‘;
Respuesta = ‘El valor es: ‘;
BEGIN
Write(Mensaje);
{Escribe en pantalla el mensaje definido como constante}
ReadLn(Entero);
{Lee un valor de teclado y lo almacena en la variable Entero}
WriteLn(Respuesta, Entero);
Readkey;
{Escribe en pantalla el contenido de Respuesta y el valor que se ingresó de te-
clado}
END.
Compilación al disco
Para poder ejecutar un programa sin necesidad de llamar al entorno integrado de desarro-
llo de Turbo Pascal es necesario compilar el programa al disco.
Para hacer esto es necesario activar la opción Destination a Disk, esto se hace entrando
al menú Compile, se selecciona la opción Destination y se presiona [Enter], de esta forma se cam-
7
bia el destino de compilación de memoria a disco o viceversa (Dependiendo de la opción seleccio-
nada actualmente).
Tipos de datos
Estructuras de datos
Entero
Real
Estándar
Caracter
Datos Simples Lógico
Subrango
Definidos por el usuario
Enumerado
Array (vectores)
Registros
Simples o Estáticos Archivos
Conjuntos
Cadenas
Datos Estructurados
Pilas
Listas Lineales Colas
Compuestos o Listas enlazadas
Dinámicos
Arboles
Listas No Lineales
Grafos
Variables y constantes
Los tipos de datos que manejaremos en nuestro programa pueden ser de dos clases: varia-
bles o constantes.
Un ejemplo de una variable es cuando vamos a sumar dos números que serán introducidos
por el usuario del programa, éste puede introducir dos valores cualesquiera y no sería nada útil
restringirlo a dos valores predefinidos, así que dejamos que use los valores que el necesite sumar.
Las variables y constantes pueden ser de todos los tipos vistos anteriormente: numéricos
tanto enteros como reales, caracteres, cadenas de caracteres, etc.
CONST pi = 3.1415926;
Es posible declarar varias constantes sucesivamente, puede ser una por renglón o varias en
un solo renglón. Cuando se hace fue, la palabra CONST solo se pone una sola vez como cabecera y
a continuación todas las constantes por definir. Ejemplo:
CONST
PI = 3.1415926;
Nombre = ‘Juan Gutiérrez’;
Unidad = 1;
La declaración de variables se lleva a cabo de la misma forma, solo que en lugar de la pa-
labra CONST utilizamos la palabra VAR, y en lugar de “= valor;”, utilizamos : tipo , sustituyendo
“tipo” por alguna clase válida de datos en Turbo Pascal. Ejemplo:
Tipos de datos
Un programa debe ser capaz de manejar diferentes tipos de datos, como pueden ser núme-
ros enteros, reales, caracteres, cadenas de caracteres, etc. Para lograr el manejo de toda esta in-
formación Turbo Pascal provee diferentes tipos de datos para los identificadores que se utilizarán.
Algunos de los más importantes se citan en seguida:
Tipos enteros
En esta categoría Turbo Pascal cuenta con 5 tipos diferentes, cada uno abarca un rango
específico de valores y utilizan una diferente cantidad de memoria dependiendo de ese rango. Na-
turalmente el trabajar con rangos menores nos ofrece una mayor velocidad y menor espacio en
memoria, pero si se utilizan enteros largos se cuenta con mayor presición. Los tipos de enteros en
Turbo Pascal son:
Cantidad := $10;
Tipos reales
9
Los números reales son aquellos que cuentan con una parte decimal. En Turbo Pascal con-
tamos con varios tipos de datos reales, pero no se puede utilizar, mas que el tipo real, en máqui-
nas que no cuenten con un coprocesador matemático. Los tipos de datos reales son:
Tipo Rango de valores que acepta
Real 2.9E-39 a 1.7E38
Single 1.5E-45 a 3.4E38
Double 5.0E-324 a 1.7E308
Extended 1.9E-4851 a 1.1E4932
Comp -9.2E18 a 9.2E18
Los números reales deben llevar por fuerza al menos un dígito de cada lado del punto de-
cimal así sea éste un cero. Como ejemplo, el número 5 debe representarse como: 5.0, el .5 como
0.5 , etc.
En este tipo de datos se utiliza la notación científica, que es igual a la de las calculadoras, el
dígito que se encuentra a continuación de la E representa la potencia a la que se elevará el número
10 para multiplicarlo por la cantidad a la izquierda de dicha E:
Los caracteres son cada uno de los símbolos que forman el código ASCII, el tipo estándar
de Pascal para estos datos es Char. Los caracteres se especifican entre comillas inglesas:
‘a’ ‘B’ ‘2’ ‘#’
El tipo Char es un tipo ordinal de Pascal, fue quiere decir que sus elementos válidos siguen
una secuencia ordenada de valores individuales. La secuencia de caracteres para este tipo corres-
ponden al número del código ASCII, del 0 al 255.
Es posible acceder a cada uno de los caracteres utilizando un signo # antes de su valor
correspondiente, por ejemplo, la letra A puede ser representada como #65, el retorno de carro, o
enter, se representa como #13, y así cualquier fue.
Tipo cadena
Las cadenas son secuencias de caracteres o arreglos que tienen una longitud máxima de
255 caracteres. Se definen entre apostrofes. El tipo de Pascal para las cadenas es String.
PROGRAM Cadena;
VAR
Nombre : String;
BEGIN
Nombre := ‘Julio Mármol’;
WriteLn (Nombre);
Readln;
END.
Este programa guarda la cadena ‘Julio Mármol’ en la variable definida como tipo string, y la
visualiza en la pantalla por medio de la instrucción WriteLn.
El tamaño por defecto para un tipo string es de 255 caracteres, pero es posible definir uno
mas pequeño utilizando el siguiente formato:
Variable : String[Tamaño];
Es posible acceder a un solo fue de una cadena utilizando inmediatamente después del
nombre de la misma la posición del fue encerrada entre corchetes. Por ejemplo:
10
VAR
Nombre : String[30];
{Permite un máximo de 30 caracteres en la variable}
BEGIN
Nombre := ‘Pablo Abdala’;
WriteLn (Nombre[5]); {Visualiza el 5to de la cadena}
Readln;
END.
Tipos lógicos
Este tipo proviene del algebra de Boole, como indica su nombre. Contiene solamente los
dos valores lógicos predefinidos: False (falso) y True (verdadero). El tipo boolean es particular-
mente útil en tareas de control de bucles y selecciones.
A B A and B A or B not A
False False False False True
False True False True True
True False False True False
True True True True False
Operadores relacionales
= igual
<> distinto
< menor
<= menor o igual
> mayor
>= mayor o igual
Operaciones básicas
Las operaciones básicas en Turbo Pascal están formadas por dos partes: el operador y los
operandos.
Un operador es el símbolo que le indica al programa que operación realizará y los operan-
dos son los datos sobre los cuales se efectuará la operación.
Operador Operación
+ Suma
- Resta
* Multiplicación
/ División
El tipo de datos que pascal regresa como resultado de una operación dependerá del tipo de
datos usados como operandos. Por ejemplo, la suma de dos enteros da como resultado otro ente-
ro, y la suma de dos números reales da como resultado otro número real.
11
Asignación o igualación
La operación de asignación es una de las más utilizadas en Turbo Pascal ya que nos permi-
te darle un valor determinado a las variables que declaramos en el programa o lo que es lo mismo,
igualarla a algún valor determinado.
El símbolo utilizado para la operación es los dos puntos seguidos por un signo de igual := , a la
izquierda de dicho símbolo se coloca el identificador al que se le asignará un nuevo valor y a la
derecha se colocará un identificador o algún valor directo que se almacenará en el primer identifi-
cador. Ejemplo:
Es indispensable para todo programa que cuente con la capacidad de manejar entradas y salidas de
información, ya que sin estas capacidades sería un programa inútil.
Estructuras de Control
Sentencias compuestas
Las sentencias compuestas son grupos de sentencias, separadas cada una por un punto y
coma “;” que son tratadas como una sola sentencia.
PROGRAM Prueba;
BEGIN
WriteLn(‘Primera línea de una sentencia compuesta’);
WriteLn(‘Segunda línea de una sentencia compuesta’);
WriteLn(‘Tercera línea de una sentencia compuesta’);
END.
El punto y coma que se encuentra antes de la palabra reservada END puede ser suprimido
sin afectar a la compilación. En ocasiones es necesario repetir un determinado número de veces la
ejecución de una sentencia, ya sea sencilla o compuesta, para realizar esta tarea Turbo Pascal
cuenta con instrucciones específicas para el tipo de repetición que se requiera.
Ciclos FOR
El ciclo FOR repite una sentencia un determinado número de veces que se indica al mo-
mento de llamar al ciclo. Lo que hace FOR es que incrementa una variable en uno desde un valor
inicial hasta un valor final ejecutando en cada incremento la sentencia que se quiere repetir. Su
sintaxis es:
Donde: Sentencias
12
Una de las limitaciones de los ciclos FOR es que una vez iniciado el ciclo se ejecutará el
número de veces predefinido sin posibilidad de agregar o eliminar ciclos.
PROGRAM Ciclo_FOR;
VAR
Numero : Integer;
BEGIN
FOR Numero := 1 to 50 DO
WriteLn(Numero); readln;
END.
Es posible hacer que un ciclo cuente hacia atrás, es decir que la variable en lugar de incre-
mentarse se decremente. Para esto cambiamos la palabra TO por DOWNTO, y colocamos el valor
mayor a la izquierda y el menor a la derecha. Ejemplo:
PROGRAM Ciclo_FOR_2;
VAR
Numero : Integer;
BEGIN
FOR Numero := 50 DOWNTO 1 DO
WriteLn(Numero);
Readln;
END.
Ciclos WHILE
Los ciclos WHILE ofrecen la ventaja de que la ejecución se realiza mientras se cumpla una
condición, por lo tanto es posible controlar el número de repeticiones una vez iniciado el ciclo. Su
sintaxis es:
Donde condición es la condición que se evaluará, mientras ésta sea verdadera se ejecutará
la instrucción, que es una sentencia simple o compuesta.
Pseudocódigo
Sentencia; Sentencias
Sentencia; Sentencias
Fin Mientras
Un programa que escriba los números del 1 al 50, utilizando el ciclo WHILE se vería como sigue:
Pascal Pseudocódigo
13
BEGIN Numero:=1
Numero := 1;
WHILE Numero <= 50 DO Mientras numero <=1 Hacer
BEGIN
WriteLn (Numero);
Numero := Numero +1; Escribir (numero)
END;
Readln; Numero:= Numero +1
END.
Fin Mientras
Fin
Al final del programa la variable Número guardará el valor 51, que fue el valor que no cum-
plió con la condición establecida en el ciclo WHILE.
Ciclos REPEAT-UNTIL
Este tipo de ciclos es muy parecido a los ciclos WHILE, la diferencia entre ambos es que en
WHILE la condición se evalúa al principio del ciclo, en cambio en REPEAT-UNTIL se evalúa al final,
lo que significa que en un ciclo REPEAT-UNTIL la sentencia se ejecutará por lo menos una vez, cosa
que puede no ocurrir en el ciclo WHILE.
Repeat Pseudocódigo
Sentencia; Repetir
Sentencia; Sentencias
Fin Repetir
Ejemplo:
Pseudocódigo
Pascal Inicio
PROGRAM Ciclo_RepeatUntil;
VAR Numero := 1
Numero : Integer;
BEGIN Repetir
Numero := 1;
REPEAT
WriteLn (Numero); Numero:= numero +1
Numero := Numero + 1;
UNTIL Numero = 50;
Hasta que Numero =50
Readln;
END.
Fin Repetir
Fin
Los Bucles condicionales o controlados por condición se utilizan cuando no se sabe, a priori,
el número exacto de iteraciones a realizar. Existen diferentes técnicas para realizar el control del
bucle.
Suma:= 0;
Writeln(‘¿ Existen mas números en la lista?) (S/N)’);
Readln (respuesta);
While (respuesta = ‘S’ ) or (respuesta =’s’)
14
El bucle se realizará mientras que la respuesta a las sentencias readln sea distinta de S o
de s.
2. Valor centinela
Un centinela es un valor especial utilizado para señalar el final de una lista de datos. El va-
lor elegido debe ser totalmente distinto de los posibles valores de la lista para que se pueda
utilizar para señalar el final de la lista. Por ejemplo, si el bucle lee una lista de números po-
sitivos, un número negativo se puede utilizar como valor centinela para indicar el final de la
lista. Un bucle como el siguiente se puede utilizar para sumar una lista de números negati-
vos.
Suma:=0;
Realdn(numero);
While numero >=0 do
Begin
Suma:= suma+numero;
Readln(numero);
End;
Para sumar los números 1,2,3,4,5 -1. El número se lee pero no se suma. Con el valor centi-
nela, lo que exige una sentencia Read antes del bucle while, de modo que en la expresión
lógica Numero >=0 la variable numero tenga un valor definido en la primera sentencia de
asignación.
Una bandera o interruptor (flag) es una variable lógica que se utiliza para conservar el
estado (verdadero o falso) de una condición. Se denomina bandera o interruptor por aso-
ciarse a un interruptor (encendido / apagado) o a una bandera (arriba /abajo). El valor del
interruptor debe inicializarse antes de comenzar el bucle y debe cambiar su estado (valor)
dentro del cuerpo del bucle para preparar la siguiente interacción.
Var
Positivo: boolean;
Begin
Positivo: false;
While not positvo do
Begin
Writeln(‘ introducir un entero mayor a cero’);
Readln(n);
End;
End;
Para crear un buen programa es necesario dotarlo con capacidad de decisión con base en
las variables o eventos definidos por el programador, para que el programa sea aplicable en un
entorno más generalizado y no solo para un problema específico.
Para lograr este control se cuenta con las estructuras de control que, en Turbo Pascal, son
las siguientes:
Sentencia IF...THEN...ELSE
15
Donde condición es la expresión que se evaluará, en caso de ser verdadera se ejecutará la
sentencia dada como instrucción, en caso de que la condición sea falsa se ejecutara la sentencia
dada como otro.
Ejemplo:
Pseudocódigo
Pascal
Inicio
PROGRAM IF_THEN_ELSE;
Para Contador:=1 hasta 50 hacer
VAR
Contador : Integer; Si Contador>10 entonces
BEGIN
FOR contador := 1 to 50 DO Escribir (contador)
BEGIN
IF contador > 10 THEN WriteLn(Contador) Sino
ELSE WriteLn(‘*’);
END; Escribir (‘*’)
Readln;
END. Finsi
FinPara
Fin
En este pequeño programa la variable Contador se incrementará desde 1 hasta 50, la sen-
tencia condicional IF verificará si es verdad que Contador es mayor a 10, de ser así se escribirá en
pantalla el valor de la variable, de lo contrario se escribirá en la pantalla un fue “*”. Como el conta-
dor inicia desde 1, tenemos que se imprimirán 10 asteriscos antes del primer número, que será el
11, valor que si cumple la condición “Contador > 10” (la hace verdadera).
La sección ELSE con sus correspondientes sentencias son opcionales y pueden omitirse en
caso de no necesitarse.
Sentencias IF anidadas
Es posible utilizar en una expresión del tipo IF..THEN..ELSE una sentencia compuesta como
la sentencia a ejecutarse en caso de que la condición sea verdadera, así como en la sentencia pos-
terior a un ELSE, de esta forma podemos utilizar otra sentencia IF..THEN..ELSE dentro de la ante-
rior, para de esta forma evaluar varias condiciones una dentro de otra.
Ejemplo:
Selecciones CASE
Esta forma de control se utiliza cuando se va a evaluar una expresión que puede contener
varios datos diferentes y en cada dato deberá realizar una acción especial. Por ejemplo, si se crea
un menú con diferentes opciones se realizará un determinado proceso para cada acción, aunque la
selección por parte del usuario se haga desde el mismo lugar.
PROGRAM Ejemplo_Case;
VAR
Numero : Integer;
BEGIN
WriteLn(‘Introduzca un número entero del 1 al 5: ‘);
ReadLn(Numero);
CASE Numero OF
1 : WriteLn(‘El número fue 1’);
2 : WriteLn(‘El número fue 2’);
3 : WriteLn(‘El número fue 3’);
4 : WriteLn(‘El número fue 4’);
5 : WriteLn(‘El número fue 5’)
16
Procedimientos y Funciones
Definición de Procedimiento
Por ejemplo, es muy común que se quiera visualizar un título determinado varias veces en
un programa, para evitar teclear ese título en nuestro programa fuente todas las veces que sea
necesario creamos un procedimiento llamado “Titulo” que se encargará de escribirlo.
Una vez creado un procedimiento actuará como una instrucción más de Turbo Pascal, y
para ejecutarlo únicamente teclearemos el nombre de dicho procedimiento.
Para poder ejecutar un procedimiento es necesario declararlo en el programa que se este utilizan-
do.
El primer paso para crear un procedimiento es saber que queremos que haga. Una vez definiendo
este punto declaramos el procedimiento después de haber declarado variables y constantes, antes
del cuerpo del programa principal. La palabra reservada para su declaración es Procedure seguida
del nombre del procedimiento. Vamos a crear un procedimiento encargado de escribir en pantalla el
enunciado “Programa de Turbo Pascal”:
PROCEDURE Titulo;
BEGIN
WriteLn (‘Programa de Turbo Pascal’);
END;
Una vez declarado el procedimiento es posible utilizarlo como una instrucción de Turbo
Pascal. Al uso de un procedimiento se le conoce como llamada al procedimiento.
PROGRAM Procedimientos;
PROCEDURE Titulo;
BEGIN
WriteLn (‘Programa de Turbo Pascal’);
END;
BEGIN
WriteLn (‘Programa ejemplo del uso de procedimientos’);
Titulo;
{Llama al procedimiento}
WriteLn;
Titulo;
{Vuelve a llamar al procedimiento}
END;
En Turbo Pascal cada identificador tiene un campo de acción, solo dentro de éste campo es
posible utilizarlo. Los ejemplos más claros son las variables, que pueden ser globales o locales. La
diferencia reside en que una variable global puede ser utilizada por cualquier parte del programa,
incluyendo a todos los procedimientos, en cambio una variable local puede ser utilizada únicamente
por el procedimiento en el que esta declarada, el programa principal y los otros procedimientos la
toman como inexistente.
Ejemplo:
PROGRAM Variables;
17
Uses crt;
VAR
Hola : String;
PROCEDURE prueba;
VAR
Adios : String;
BEGIN
{En esta sección si es posible usar la variable Hola}
Adios := ‘Chau, que vaya bien!!’;
WriteLn (Adios);
END;
BEGIN
Clrscr;
{En esta sección no se reconoce a la variable Adios}
Hola := ‘Hola, bienvenidos al programa’;
WriteLn (Hola);
WriteLn (Adios);
{Al compilar el programa se generará un error ya que la variable Adios es inexis-
tente en esta sección, para eliminar el problema quites esta línea}
prueba; readln;
END.
Es posible darle el mismo nombre a una variable local y a una global en el mismo progra-
ma, pero entonces el procedimiento no podrá utilizar la variable global ya que le da preferencia a
las locales sobre las globales. Por ejemplo:
PROGRAM Variables_2;
Uses crt;
VAR
Saludo : String;
PROCEDURE Mensaje;
VAR
Saludo : String;
BEGIN
Saludo := ‘Este mensaje solo es válido para el procedimiento “Mensaje”’;
WriteLn(‘Saludo’);
END;
BEGIN
Clrscr;
Saludo := ‘Primer saludo (Global)’;
WriteLn (Saludo);
Mensaje;
WriteLn (Saludo); {Se escribirá el mensaje “Primer saludo”}
Readkey;
END.
Parámetros
Para poder pasar información entre el programa principal y procedimientos, o entre los mismos
procedimientos usamos los parámetros, que son los canales de comunicación de datos.
Los parámetros son opcionales y si no se necesitan no se deben usar. Para utilizarlos es necesario
declararlos son la siguiente sintaxis:
La lista de parámetros esta compuesta de los nombres de los mismos y del tipo de datos que re-
presentan, los del mismo tipo se separan con comas “,” y cada tipo diferente se separa con punto y
coma “;”. Ejemplo:
Para llamar a un procedimiento que utiliza parámetros se pueden utilizar como tales otras variables
o constantes, siempre y cuando sean del mismo tipo que los declarados. Ejemplo:
PROGRAM Ejemplo;
Uses crt;
VAR
18
BEGIN
Clrscr;
Saludo := ‘Bienvenidos al programa’;
Imprime_5 (Saludo);
{llama al procedimiento Imprime_5, usando como}
{parámetro la variable Saludo }
Imprime_5 (‘Fin’);
{Utiliza la cadena “fin” como parámetro }
Readkey;
END.
Es válido crear un procedimiento que llame a otro procedimiento siempre y cuando el procedimien-
to llamado haya sido declarado antes del que lo usará.
Recordando los dos aspectos de definición y llamada que encontramos en los subprogramas, tene-
mos que distinguir dos tipos de parámetros.
Cuando se define un subprograma es necesario dar nombres a los parámetros para poder mencio-
narlos. A los parámetros utilizados en la definición de procedimientos y funciones se les denomina
parámetros formales. A veces se llaman también ficticios, porque se utilizan solamente a efectos de
la definición pero no con valores reales.
Antes de entrar en materia conviene que nos fijemos en los procedimientos Read y Write que va-
mos a aplicar a una cierta variable entera a la que llamaremos a.
Supongamos, en primer lugar, que esta variable tiene un valor que le ha sido asignado previamente
en el programa, por ejemplo 10, y a continuación esta variable es pasada como parámetro al pro-
cedimiento Write. Este procedimiento recibe el valor de a y lo escribe en la pantalla. La acción de
Write no modifica el valor de a, que sigue siendo 10.
Ejemplo:
En cambio, supongamos ahora que utilizamos el procedimiento Read con la misma variable a, y que
el usuario escribe por el teclado un valor distinto al que tenía a, por ejemplo 20. Como consecuen-
cia de la llamada, el valor de la variable a es modificado, de 10 a 20.
Estas diferencias se deben a que en Pascal existen dos formas de pasar parámetros que se diferen-
cian en la forma en que se sustituyen los parámetros formales por los reales al efectuarse la llama-
da. Estos mecanismos se conocen como:
19
En este caso, se calcula el valor de los parámetros reales y después se copia su valor en los
formales, por lo tanto los parámetros reales deben ser expresiones cuyo valor pueda ser calculado.
Este mecanismo se llama paso de parámetros por valor y tiene como consecuencia que, si se modi-
fican los parámetros formales en el cuerpo del subprograma, los parámetros reales no se ven afec-
tados.
Dicho de otra forma, no hay transferencia de información desde el subprograma al programa en el
punto de su llamada. Por lo tanto, los parámetros por valor actúan solo como datos de entrada al
subprograma.
En este otro caso, se hacen coincidir en el mismo espacio de memoria los parámetros reales y los
formales, luego los parámetros reales han de ser variables. Este segundo mecanismo se denomina
paso de parámetros por referencia (también por dirección o por variable), y tiene como consecuen-
cia que toda modificación de los parámetros formales se efectúa directamente sobre los parámetros
reales, y esos cambios permanecen al finalizar la llamada. Es decir, que se puede producir una
transferencia de información desde el subprograma al programa, o dicho de otro modo, que los
parámetros por referencia no solo actúan como datos de entrada, sino que también pueden repre-
sentar resultados de salida del procedimiento.
Para distinguir los parámetros pasados por valor de los pasados por variable, estos últimos van
precedidos de la palabra reservada var en la definición del subprograma.
Veamos las diferencias entre parámetros por valor y referencia mediante un ejemplo consistente en
un procedimiento que incrementa el valor de una variable en una unidad. En el caso de parámetros
por valor, el incremento tiene efectos únicamente dentro del procedimiento, mientras que en el
caso de parámetros por referencia los efectos se extienden también al programa principal.
w:= 5;
WriteLn(w); 5
EscribirSiguiente(w); 6
WriteLn(w) 5
En este ejemplo, la variable w que hace de parámetro real tiene inicialmente el valor 5, como pue-
de verse en la salida. Este valor se copia en el parámetro formal v y dentro del procedimiento v se
incrementa en una unidad. Sin embargo, por tratarse de parámetros por valor, este cambio en v no
tiene efecto sobre el parámetro real w, lo que comprobamos al volver al programa principal y escri-
bir su valor que sigue siendo 5.
w:= 5
WriteLn(w); 5
IncrementarYescribir(w); 6
WriteLn(w) 6
En este segundo caso, al tratarse de parámetros por referencia, el espacio en memoria de w coin-
cide durante la llamada con el de v; por ello, el incremento de v se efectúa también sobre w. Al
terminar el procedimiento, w tiene el valor 6.
20
Begin
{2A}
Repeat
Write('Escriba un entero positivo: ');
ReadLn(n)
until n >= 0
{2B}
end; {LeerNumPos}
var
i, prodAcum: integer;
begin
{4A}
prodAcum:= 1;
for i:= 2 to num do
prodAcum:= prodAcum * i;
Fac:= prodAcum
{4B}
end; {Fac}
{3}
WriteLn('El factorial de ', numero, ' es ', Fac(numero))
{5}
end. {DemoParametros}
Al comienzo del programa solo se dispone de la variable número que esta indefinida en el
punto {1} del programa (y en su correspondiente estado de memoria).
El programa llama al procedimiento LeerNumPos y le pasa por referencia el parámetro real número.
Al producirse la llamada, la ejecución del programa principal queda suspendida y se pasan a ejecu-
tar las instrucciones del procedimiento LeerNumPos. Como número se ha pasado por referencia, en
la llamada, número y n, que es el parámetro formal de LeerNumPos, coinciden en memoria.
Dado que inicialmente numero esta indefinido también lo estaría n en el estado
{2A}, al principio de LeerNumPos.
Una vez activado LeerNumPos, éste pide al usuario un número entero positivo, que queda
asignado a n. Supongamos que el valor introducido ha sido, por ejemplo, 5. En el punto {2B}, este
valor es el que queda asignado a n y a número al coincidir ambos.
Al terminar el procedimiento LeerNumPos, se reanuda la ejecución del programa principal,
con lo cual n desaparece de la memoria (estado {3}).
Le llega el turno a la instrucción de escritura, que hace una llamada a Fac pasándole por
valor el contenido de número. De nuevo, al producirse la llamada, la ejecución del programa princi-
pal queda suspendida, hasta que Fac termine y devuelva el resultado.
La funcion dispone del parámetro formal num, que recibe el contenido de numero, y de dos
variables propias i y prodAcum, que al comenzar la funcion (estado {4A}) están indefinidas.
Al terminar el bucle for, se ha acumulado en prodAcum el producto 2 * 3 *4 * 5 sucesiva-
mente, por lo que su valor es 120. Dicho valor, que corresponde al del factorial pedido, es asignado
al nombre de la funcion (estado {4B}), quien lo devuelve al programa principal.
El nombre de la función se utiliza como un almacenamiento temporal del resultado obtenido para
transferirlo al programa principal. Aunque puede ser asignado como una variable, el parecido entre
ambas termina aquí. El nombre de la función no es una variable y no puede ser utilizado como tal
(es decir sin parámetros) a la derecha de la instrucción de asignación.
21
Program Principal procedure P (parámetros);
Declaraciones y definiciones Declaraciones y definiciones
begin begin
….. …..
llamada al procedimiento P instrucciones
…. ….
end. end;
En Pascal el funcionamiento de los parámetros es el mismo tanto para procedimientos como para
funciones. Sin embargo, la utilización de las funciones es la de calcular un valor, por lo que no tiene
sentido que estas utilicen parámetros por referencia.
Las funciones son, al igual que los procedimientos, un conjunto de sentencias que se ejecutan
constantemente, la diferencia entre éstas y los procedimientos es que las funciones regresan un
valor. La declaración de las funciones se realiza de la siguiente forma:
A continuación se escribe el contenido de la función como una sentencia normal (sencilla o com-
puesta), normalmente terminando con la igualación de la función al valor que regresará. Ejemplo:
Como las funciones devuelven un valor específico la forma más usual de utilizarlas es por medio de
asignaciones de una variable a la función. Ejemplo:
PROGRAM Funciones;
Uses Crt;
VAR
X, Y, Z : Real;
BEGIN
clrscr;
X := 5.89;
Y := 9.23;
Z := Promedio (X, Y);
{Iguala Z al valor devuelto por la función Promedio}
WriteLn(‘El promedio de ‘,X,’ y ‘,Y,’ es: ‘,Z:2:2);
Readln;
END.
Sentencias de Pascal
Nombre Descripción
Writeln Escribe un texto o variable por pantalla.
Uses crt;
var
n : integer;
begin
clrscr;
Write('Ejemplo de salida de texto por pantalla ');
N:=12;
Writeln('Acá aparece el valor de la variabel N',N);
Writeln('Pulse <Enter> para salir');
Readln;
22
BEGIN
Suma := 12 + 8;
Resta := 12 – 8;
multiplicacion := 12 * 8;
Division := 12/8;
Cociente_Entero := 12 DIV 8;
Resto_Entero := 12 MOD 8;
WriteLn (‘La suma de 12 + 8 es igual a: ‘,Suma);
WriteLn (‘La resta de 12 – 8 es igual a: ‘,Resta);
WriteLn (‘La multiplicación de 12 * 8 es igual a: ‘,multiplicacion);
WriteLn (‘La división de 12 / 8 es igual a: ‘,Division);
WriteLn (‘La división entera de 12 / 8 es igual a: ‘,Cociente_Entero);
WriteLn (‘El residuo de la división entera de 12 / 8 es:
‘,Resto_Entero); Readln;
END.
INC Incrementa en 1 el contenido de cualquier variable de tipo entero es lo mismo en un
programa teclear: Variable := Variable + 1;
Ejemplo: Inc(Variable);
DEC Disminuye en 1 el valor de cualquier variable de tipo entero que se le indique. Ejemplo:
DEC (Variable);
Round Redondea un número real al entero más cercano y devuelve el resultado como un
número real.
Trunc Elimina la parte decimal del número real y devuelve el resultado como tipo entero.
PROGRAM Redondeo;
uses crt;
VAR
Entero : Integer;
ValorReal1, ValorReal2 : Real;
BEGIN
Clrscr;
ValorReal1 := 123.435;
ValorReal2 := Round(ValorReal1);
Entero := Trunc (ValorReal1);
WriteLn('El resultado de la función Round sobre el valor');
WriteLn(ValorReal1:2:2, ' es: ','ValorReal2:2:2, ' (Número real)');
WriteLn('El resultado de la función Trunc sobre el valor');
WriteLn(ValorReal1, ' es: ', Entero, ' (Número entero)');
readkey;
END.
Exp Devuelve la funcion exponencial e^x
Ln Devuelve el logaritmo natural de x.
Es posible crear una función con base en estas dos para calcular el resultado de un
número x elevado a una potencia n, la cual nos quedaría así:
FUNCTION Potencia(x,n:Real):Real;
BEGIN
Potencia := Exp(n *Ln(x))
END;
El uso de esta función en un programa podría ser:
PROGRAM Potenciacion;
FUNCTION Potencia(x, n : Real) : Real;
BEGIN
Potencia := Exp(n * Ln(x))
END;
BEGIN
23
WriteLn('El resultado de 5 elevado a 3 es: ',Potencia(5,3));
Readln;
END.
Sqr Devuelve el cuadrado del valor utilizado como parámetro.
Sqrt Regresa la raíz cuadrada del valor dado como parámetro, el resultado siempre es real
aunque se utilice un número de tipo entero como argumento
PROGRAM Exponentes;
VAR
Dato, Cuadrado : Integer;
Raiz : Real;
BEGIN
Dato := 45;
Cuadrado := Sqr(Dato);
Raiz := Sqrt(Dato);
WriteLn(' El cuadrado de ',Dato, ' es: ',Cuadrado);
WriteLn('La raíz cuadrada de ',Dato,' es: ',Raiz);Readln;
END.
Random Genera un número aleatorio de tipo real comprendido entre 0 y 1
Random Genera un entero aleatorio entre 0 y x.
(x)
Sin embargo el uso de la función Random es en ocasiones insuficiente por si sola para la
generación de valores aleatorios ya que los números son realmente pseudos aleatorios,
y cada vez que se ejecute el programa se obtendrían los mismos valores. Para evitar
esto debemos utilizar en todo programa que utilice valores aleatorios el procedimiento
Randomize, que inicializa el generador de números aleatorios, en otras palabras, ase-
gura que los números que obtendrá serán diferentes cada vez que se ejecute el pro-
grama.
Es recomendable usar este procedimiento antes de cada función random del programa.
PROGRAM Nums_Aleatorios;
VAR
y, x : Integer;
BEGIN
Y:=100; Randomize;
WriteLn('Lista de números aleatorios');
For x := 1 to 20 do
WriteLn(Random(y));
Readkey;END.
Abs Devuelve el valor absoluto del argumento.
var
r: Real;
i: Integer;
begin
r := Abs(-2.3); { 2.3 }
i := Abs(-157); { 157 }
end.
ArcTan Devuelve el arco tangente del argumento.
var
R: Real;
begin
R := ArcTan(Pi); writeln(R:2:2); readln;
end.
Cos Devuelve el coseno del argumento.
var
R: Real;
begin
R := cos(180); writeln(R:2:2); readln;
end.
Frac Devuelve la parte decimal del argumento.
var
R: Real;
begin
R := Frac(123.456); { 0.456 }
R := Frac(-123.456); { -0.456 }
end.
Int Devuelve la parte entera del argumento.
var R: Real;
begin
R := Int(123.456); { 123.0 }
24
La comparación de cadenas es una operación muy común en Turbo Pascal; estas comparaciones se
realizan con base en el orden del código ASCII, por ejemplo la cadena 'Prueba' es menor a la cade-
na 'prueba' ya que el valor del código ASCII de P es 80 y el de p es 112.
Así también podemos diferenciar las cadenas con respecto a su tamaño: 'Hola' es menor a 'Bienve-
nido'
Existe un caso especial de una cadena, que es cuando no tiene ningún caracter en ella, en ese
momento decimos que es una cadena nula o vacía. Se recomienda ampliamente inicializar todos los
valores de las cadenas a cadenas nulas al inicio del programa, ya que podrían contener datos ex-
traños e indeseables.
Es posible leer cada elemento de una cadena por separado, por ejemplo, si en la variable Calle
almacenamos la cadena ' Av. Independencia' es posible asignar a otra cadena el valor de Ca-
lle[x] donde x es el caracter de la cadena que se quiere leer, así también es posible visualizar el
caracter en pantalla usando la instrucción WriteLn.
Ejemplo:
PROGRAM Caracter;
VAR
Calle : String;
Letra : String;
BEGIN
Calle := 'Av. Independencia';
WriteLn(Calle[2]); {Visualiza el segundo caracter de Calle}
Letra := Calle[1]; {Guarda en Letra el primer caracter de Calle}
WriteLn(Caracter)
END.
Para conocer la longitud de una cadena utilizamos la función Length, la longitud es la cantidad de
caracteres que contiene la cadena en un momento determinado.
Su sintaxis es:
Nombre Descripción
Length Devuelve la longitud de la cantidad de caracteres que contiene la cadena en un mo-
mento determinado.
PROGRAM Funcion_Length;
VAR
Cadena : String;
BEGIN
Cadena := 'Prueba de longitud';
WriteLn ('Longitud de la cadena: ', Length (Cadena)); readln;
END.
25
Operador + Es una de las formas más sencillas de unir dos cadenas y se maneja exactamente como
una suma, la única limitante es que en caso de ser mayor la longitud resultante de la
suma que la longitud que pueda manejar la variable en que se almacenará se truncarán
los caracteres sobrantes.
Ejemplo:
S:=’Pablo Achaval’;
Insert(‘Abdala’,S,7);
( inserta a la variable S los caracteres Abdala, quedando la va-
riable S con el contenido: Pablo Abdala Achaval)
Delete Elimina un determinado número de caracteres de una cadena: Delete (Cadena, Ini-
cio, Número);
Ejemplo:
Cad1:= ‘computer’;
Delete(cad1,4,6);
(cad1 queda con el “com” ya que solo se puede borrar 5 caracte-
res)
UpCase Regresa la letra mayúscula correspondiente al caracter dado como parámetro.
Ejemlo:
Upcase(‘N’) devuelve ‘N’
Upcase(‘n’); devuelve ‘N’
Str Obtiene una cadena (string) a partir de un valor numérico
Var
Cad1:string;
N:integer;
begin
N:= 123;
Srt(n,cad1); {resultado: cad1= 12345 de tipo string.}
End.
Val Convierte una cadena en un valor de tipo numérico, el problema con esta función es que
si la cadena no esta formada exclusivamente de números siguiendo las reglas para el
tipo de datos que se vallan a obtener se generará un error. Sintaxis: Val (Cad, Num,
Código) ;
Cad es la variable string; Num es en donde se guardará el valor y Código es una varia-
ble de tipo entero (Integer) que contendrá el valor de 0 si no hubo errores y un valor
diferente en caso de que no se haya podido convertir la cadena, el valor de Código re-
presenta la posición de la cadena donde ocurrió el error.
Ch:= ‘23.7’ ;
Val(ch,nombre,código);
If código =0 then
Writeln(nombre,’ ’, código)
Else
Writeln(código)
Ch:=’23x.7’;
26
Hasta el momento hemos trabajado todo el rato con tipos de datos b·sicos de Pascal: boo-
leanos, caracteres, string, enteros y reales. La mayorÌa de aplicaciones de la vida real no emplean
datos de uno en uno sino que emplean grupos de datos. Para resolver este problema Pascal incor-
pora un conjunto de datos estructurados: array (vectores o tablas), registros y conjuntos.
Declaraciones de tipo
Con la palabra reservada type podemos declarar nuevos tipos que podremos emplear cuando de-
claremos variables, de forma que estas variables sean del tipo definido.
type
Numero = Integer;
NumeroNoIdentico = type Integer;
var
N1 : Numero; // Esto es lo mismo que un Integer
N2 : NumeroNoIdentico; // Esto no es exactamente un Integer aunque funciona
igual
begin
...
end.
Enumeraciones
El tipo enumerado permite construir datos con propiedades ordinales. La caracterÌstica del tipo
enumerado es que sÛlo puede tomar los valores de su enumeración.
type
TMedioTransporte : (Pie, Bicicleta, Moto, Coche, Camion, Tren, Barco, Avion);
var
MedioTransporte : TMedioTransporte;
begin
MedioTransporte := Camion;
{ Sólo es posible asignar a identificadores de la enumeración }
case MedioTransporte of // Es posible ya que un enumerado es un tipo ordinal
Pie : begin
end;
Bicicleta : begin
end;
...
end;
end.
Por convención, los tipos de datos se suelen declarar con una T seguido del nombre. De
esta forma, los identificadores que empiezan por T se entiende que son tipos y no variables.
Las enumeraciones se declaran con paréntesis y seguido de identificadores separados por comas.
Datos enumerados
Sintaxis:
Type
Identificador de tipo = (const1, const2…. Constn);
Ejemplo:
27
Type
Dias_semana =( lunes, martes, miércoles, jueves, viernes, sabado, domingo);
Modalidades = (informática, construcciones, electrónica);
La variable sem1 puede tomar como
Var contenido solo los valores lunes, mar-
Sem1: dias_semana; tes, miércoles, jueves, viernes, sabado
o domingo.
M1,M2: Modalidades;
Observación: Los identificadores de los valores no deben repetirse. El siguiente ejemplo es ilegal.
Type
Dias_semana =( lunes, martes, miércoles, jueves, viernes, sabado, domingo);
Fin_semana =( sabado, domingo);
Datos Subrango
Un tipo de dato subrango puede definirse como un subintervalo de los tipos ordinal Ies aso-
ciados. La definición de un tipo por subrango indica simplemente el valor menor y mayor en el sub-
rango, teniendo en cuenta que la cota inferior no puede ser mayor que la cota superior. No se
permiten subrangos del tipo real . las variables de este tipo poseen todas las propiedades de los
tipos asociados.
Sintaxis:
Type
Ejemplo:
TYPE
Dias_semana=(lunes, martes, miércoles, jueves, viernes, sabado, domingo);
Dias_Laborables= Lunes..viernes; {Subrango de dias_semana}
Siglo_XX0 1901..2000; {Subrango de integer}
Minusculas= ‘a’..’z’: {Subrango de char}
VAr
D1,D2,D3: Dias_laborables;
Letra:Minusculas;
Un arreglo es una colección ordenada de variables todas las cuales tienen el mismo tipo
(esto es, tienen un tipo homogéneo). Cada uno de los elementos de la colección se llama compo-
nente del arreglo.
2.- Los arreglos poseen dimensión y se los reconoce por la cantidad de índices que posee el arre-
glo. Un índice indica la dimensión 1 y representa una columna de datos (arreglos unidimensiona-
les); dos índices establecen un arreglo de dimensión 2 representa una tabla de datos dispuesta en
filas y columnas (arreglos bi-dimensionales o matrices). Un arreglo de dimensión 3 (tres índices)
representa una tabla de datos en múltiples páginas, etc.
3.- Cada dimensión de los arreglos tiene un límite inferior y otro superior. Estos límites determinan
la extensión de los valores que son usados como subíndices para la dimensión. El valor de cada
I
A los tipos Integer, Bolean, Char y enumerado se los conoce como ordinales o escalare porque sus
valores posee un orden determinado (predefinidos o definidos por el programador)
28
4.- El tamaño de un arreglo (esto es, el número de componentes) se indica cuando se define el
arreglo y queda invariable a partir de ese momento. El cálculo del número de componentes se logra
multiplicando entre si la cantidad de elementos que el arreglo posee en cada dimensión.
Arreglos Unidimensionales
Los arrays son tipos de datos estructurados ampliamente utilizados, porque permiten ma-
nejar colecciones de objetos de un mismo tipo con acceso en tiempo constante, y también porque
han demostrado constituir una herramienta de enorme utilidad.
En términos generales, un vector es una secuencia, de longitud fija, formada por elementos
del mismo tipo. Teniendo en cuenta que un vector es un array de dimensión 1, su definición es
sencilla.
Ejemplo:
const
CapacidadMax = 40;
type
tCapacidad = 0..CapacidadMax;
tVagones = array[1..15] of tCapacidad;
var
vagon : tVagones;
Así, haremos referencia al número de pasajeros del vagón i-ésimo mediante vagón[i],
mientras que el total de viajeros en el tren seria
15
vagón[i]
Xi=1
Matrices
Evidentemente, la forma de definir los tipos de datos para las matrices es la misma de to-
dos los arrays, así como el modo de declarar y manipular variables de estos tipos.
En el siguiente ejemplo, que calcula el producto de dos matrices reales, se utilizan las ope-
raciones permitidas a los arrays, haciendo uso del hecho de que las matrices se pueden pasar como
parámetros en funciones y procedimientos. En un primer nivel de diseño se tiene:
Leer matrices a y b
Multiplicar matrices a y b, hallando la matriz producto prod
Mostrar la matriz prod
29
Program MultiplicacionDeMatrices ;
{El programa lee dos matrices cuadradas de dimensión N y de componentes reales y las multiplica,
mostrando la matriz producto en la pantalla}
const
N = 10;
type
tMatriz = array[1..N, 1..N] of real;
var
a, b, prod: tMatriz;
var
fil, col: 1..N;
begin
for fil:= 1 to N do
for col:= 1 to N do begin
Write('Introduzca la componente ', fil, ',', col, ' de la matriz: ');
ReadLn(mat[fil,col])
end
end; {LeerMatriz}
begin
for i:= 1 to N do
for k:= 1 to N do begin
resul[i,k]:= 0;
for j:= 1 to N do
resul[i,k]:= resul[i,k] + m1[i,j] * m2[j,k]
end {for k}
end; {MultiplicarMat}
begin
for i:= 1 to N do
for j:= 1 to N do
{Escribe mij}
WriteLn('m(', i, ',', j, ') = ', m[i,j]);
end; {EscribirMatProd}
begin
WriteLn('Lectura de la matriz A');
LeerMatriz(a);
WriteLn('Lectura de la matriz B');
LeerMatriz(b);
MultiplicarMat(a,b,prod);
EscribirMatProd(prod)
end. {MultiplicacionDeMatrices}
Matrices tridimensionales
En este caso, sería mucho más cómodo utilizar algún tipo de datos estructurado para alma-
cenar esta información de forma más compacta y manejable (por medio de instrucciones estructu-
radas). La estructura más adecuada sería la de una matriz tridimensional: la primera dimensión
30
Vectores Paralelos
Consiste en almacenar y recuperar los ítems de información asociada del mismo índice en
cada uno de los vectores paralelos.
Recordando que los componentes de los vectores deben tener el mismo tipo, se ha creado
cuatro vectores paralelos para representar la información de los departamentos. Por ejemplo los
datos del departamento 2 figuran en las segundas componentes de los cuatro vectores.
Para encontrar el listado de los propietarios que no han pagado las expensas e imprimir
apellido y nombre, departamento y deuda, se presenta el siguiente código:
Program vectores_paralelos;
Type
Vec1= array [1…100] of string [25];
Vec2= array [1…100] of real;
Vec3= array [1…100] of boolean;
Var
Begin
For i:=1 to N do
{muestra los dptos que no pagaron expensas}
If not(pago[i]) then
Writeln (ayp[i], dpto[i], expen[i]);
End.
31
ción en que se encuentra para poder trabajar con él. Los mecanismos que realizan esta función son
conocidos como algoritmos de búsqueda.
Supongamos que tenemos un vector v con n elementos (los índices son los 1. . . n) y pre-
tendemos construir una función búsqueda que encuentre un índice i de tal forma que v[i] = elem,
siendo elem el elemento que se busca. Si no existe tal índice, la función debe devolver un cero,
indicando así que el elemento elem buscado no esta en el vector v.
const
N = 100; {tamaño del vector}
type
tIntervalo = 0..N;
tVector = array [1..N] of integer;
Búsqueda secuencial
var
i: tIntervalo;
begin
i:= 0; {se inicia el contador}
repeat
i:= i + 1
until (v[i] = elem) or (i = N);
BusquedaSec:= i
else
BusquedaSec:= 0
end; {BusquedaSec}
Esta técnica puede entenderse como un caso particular de la programación con sub-
programas en la que se planteaba la resolución de un problema en términos de otros subproblemas
mas sencillos. El caso que nos ocupa en este capítulo es aquel en el que al menos uno de los sub-
problemas es una instancia del problema original.
Como, a su vez
n!= n * (n-1)!
Siendo por definición 0! = 1, lo que permite terminar correctamente los cálculos. Por ejem-
plo, al calcular el factorial de 3:
32
4 * (3 * Fac (2))
4 * (3 * (2 * Fac(1)))
4 * ( 3 * (2 * (1 * Fac(0))))
4 * (3 * (2 * (1 * 1)))
4 * (3 * (2 * 1))
4 * (3 * 2)
4 * 6
24
– Habrá un caso (o varios) tan simple que pueda resolverse directamente sin necesidad de hacer
otra llamada recursiva.
33
dejan de tener vigencia y pasa a ejecutarse la instrucción siguiente a la de llamada.
Programa
Begin
… Sub programa
… Llamada a Begin
End; subprograma …
… Llamada a
End; subprograma
…
End;
En el caso de un subprograma recursivo, cada llamada genera un nuevo ejemplar del sub-
programa con sus correspondientes objetos locales. Podemos imaginar cada ejemplar como una
copia del subprograma en ejecución. En este
proceso destacamos los siguientes detalles:
* El nuevo ejemplar del subprograma pasa a ejecutarse sobre su tabla de activación, que se
amontona sobre las de las llamadas recursivas anteriores formando la llamada pila recursiva.
* Este proceso termina cuando un ejemplar no genera mas llamadas recursivas por consistir sus
argumentos en casos básicos.
Entonces, se libera el espacio reservado para la tabla de activación de ese ejemplar, re-
anudándose las instrucciones del subprograma anterior sobre la tabla penúltima.
La sucesión de Fibonacci II
fib0 = 1
fib1 = 1
fibn = fibn-2 + fibn-1; si n>= 2
II
Sucesión de Fibonacci, en matemáticas, sucesión de números en la que cada término es igual a la
suma de los dos términos precedentes: 0, 1, 1, 2, 3, 5, 8, 13, 21, y así sucesivamente. Esta suce-
sión fue descubierta por el matemático italiano Leonardo Fibonacci. Los números de Fibonacci tie-
nen interesantes propiedades y se utilizan mucho en matemáticas. Las estructuras naturales, como
el crecimiento de hojas en espiral en algunos árboles, presentan con frecuencia la forma de la su-
cesión de Fibonacci.
34
begin
if (num = 0) or (num = 1) then
Fib:= 1
else
Fib:= Fib(num - 1) + Fib(num - 2)
end; {Fib}
Consideremos inicialmente dos discos en A que queremos pasar a B utilizando C como auxiliar. Las
operaciones por realizar son sencillas:
En la exposición mundial de Paris de 1883 el matemático francés E. Lucas presento un juego llama-
do Torres de Hanoi, que tiene una solución recursiva relativamente sencilla y que suele exponerse
como ejemplo de la potencia de la recursión para resolver ciertos problemas cuya solución es mas
compleja en forma iterativa.
B C
A
El juego estaba formado por una base con tres agujas verticales, y en una de ellas se en-
contraban engarzados unos discos de tamaño creciente formando una torre, según se muestra en
la figura. El problema por resolver consiste en trasladar todos los discos de una aguja a otra, mo-
viéndolos de uno en uno, pero con la condición de que un disco nunca descanse sobre otro menor.
En distintas fases del traslado se deberán usar las agujas como almacén temporal de discos.
Llamaremos A, B y C a cada una de las agujas sin importar el orden siempre que se mantengan los
nombres
Mover un disco de A a C
Pasar dos discos de A a B Mover un disco de A a B
Mover un disco de C a B
III
según reza la leyenda, en la ciudad de Hanoi, a orillas del río Rojo, descansa una bandeja de cobre con
tres agujas verticales de diamante. Al terminar la creación, Dios ensarto en la primera de ellas sesenta y cua-
tro discos de oro puro de tamaños decrecientes. Esta es la torre de Brahma. Desde entonces, los monjes
empeñan su sabiduría en trasladar la torre hasta la tercera aguja, moviendo los discos de uno en uno y con la
condición de que ninguno de ellos se apoye en otro de menor tamaño. La leyenda afirma que el término de
esta tarea coincidirá con el fin del mundo, aunque no parece que, por el momento, estén cerca de lograrlo.
35
Mover 1 disco de A a B
Pasar dos de A a C= Mover 1 disco de A a C
Mover 1 disco de B a C
Mover 1 disco de C a A
Pasar dos de C a B= Mover 1 disco de C a B
Mover 1 disco de A a B
En general, Pasar n discos de A a B (siendo n >=1), consiste en efectuar las siguientes operacio-
nes,
siendo 1 el caso base, que consiste en mover simplemente un disco sin generar llamada recursiva.
Ahora apreciamos claramente la naturaleza recursiva del proceso, pues para pasar n discos es pre-
ciso pasar n-1 discos (dos veces), para n-1 habrá que pasar n-2 (también dos veces) y así sucesi-
vamente.
Como ejemplo de funcionamiento, la llamada PasarDiscos(4, 'A', 'B', 'C') produce la siguiente salida:
Otro Ejemplo:
N N MOD 2 N DIV 2
23 1 11
11 1 5
5 1 2
2 0 1
1 1 0
0
36
Los registros (Record) son otro tipo de datos estructurados muy utilizados en Pascal. Su principal
utilidad reside en que pueden almacenar datos de distintos tipos, a diferencia de los demás datos
estructurados. Un registro estaría formado por varios datos (simples o estructurados) a los que
llamaremos campos del registro y que tendrían asociado un identificador al que llamaremos nombre
de campo.
Por ejemplo, supongamos que un Maestro Mayor de obras quiere tener registrados varios datos de
sus trabajadores, tales como: nombre, dirección, edad y número de D.N.I. Con los tipos de datos
que conocemos resultaría bastante difícil, ya que tendríamos que indicar que las variables dirección,
edad y D.N.I están relacionadas con el nombre de un trabajador en concreto. Para solucionarlo, se
utiliza el tipo de datos estructurado registro, de la siguiente forma:
type
tEdades = 16..65;
tDigitos = '0'..'9';
tFicha = record
nombre: array[1..30] of char;
direccion: array[1..50] of char;
edad: tEdades;
dni: array[1..8] of tDigitos
end; {tFicha}
Es conveniente destacar que el tipo de datos registro, al igual que el tipo array, es un tipo estructu-
rado de tamaño fijo; sin embargo se diferencian de ellos principalmente en que los componentes de
37
un array son todos del mismo tipo, mientras que los componentes de un registro pueden ser de
tipos distintos.
El dominio de un registro estaría formado por el producto cartesiano de los dominios de sus cam-
pos componentes.
Para poder trabajar con el tipo de datos registro es necesario saber cómo acceder a sus campos,
cómo asignarles valores y que tipo de operaciones podemos realizar con ellos:
Para acceder a los campos de los registros se utilizan construcciones de la forma nomVarRegis-
tro.nomCampo, es decir, el nombre de una variable de tipo registro seguido de un punto y el nom-
bre del campo al que se quiere acceder. Por ejemplo, si la variable f es de tipo tFicha, para acceder
a sus campos nombre, dirección, edad y D.N.I se utilizarán, respectivamente, las construcciones:
f.nombre
f.direccion
f.edad
f.dni
En este punto se debe señalar que, a diferencia de los arrays, en los cuales el acceso se realiza por
medio de índices (tantos como dimensiones tenga el array, que pueden ser el resultado de una
expresión y por tanto calculables), en los registros se accede por medio de los identificadores de
sus campos, que deben darse explícitamente.
Los tipos de los campos pueden ser tipos predefinidos o definidos por el programador mediante
una definición de tipo previa. Incluso un campo de un registro puede ser de tipo registro. Así, por
ejemplo, si queremos almacenar para cada alumno, su nombre, fecha de nacimiento y nota, podr-
íamos definir tipos y variables de la siguiente forma:
type
tMeses = (ene, feb, mar, abr, may, jun, jul, ago, sep, oct, nov, dic);
tCalificaciones = (NP, Sus, Apr, Notab, Sob, MH);
tNombre = array[1..50] of char;
tFecha = record
dia : 1..31;
mes : tMeses;
anio : 1900..2100
end; {tFecha}
tFicha = record
nombre : tNombre;
fechaNac : tFecha;
nota : tCalificaciones
end; {tFicha}
var
alumno: tFicha;
La asignación de valores a los campos se hará dependiendo del tipo de cada uno de ellos.
Así, en el ejemplo anterior, para iniciar los datos de la variable Alumno tendríamos que utilizar las
siguientes asignaciones:
Las operaciones de lectura y escritura de registros han de hacerse campo por campo, em-
pleando procedimientos o funciones especiales si el tipo del campo así lo requiere. Por ejemplo:
begin
WriteLn('Nombre: ', unAlumno.nombre);
Write('Fecha de nacimiento: ',unAlumno.fechaNac.dia);
EscribirMes(unAlumno.fechaNac.mes);
WriteLn(unAlumno.fechaNac.anio);
Write('Nota: ');
EscribirNota(unAlumno.nota)
end; {EscribirFicha}
38
Al igual que todos los tipos de datos compuestos, un registro no puede ser el resultado de
una función. Para solucionar este problema actuaremos como de costumbre, transformando la
función en un procedimiento con un parámetro por variable adicional de tipo registro que albergue
el resultado de la función. Así, por ejemplo:
begin
Write('Nombre del alumno:');
LeerNombre(unAlumno.nombre);
Write('Día de nacimiento: ');
ReadLn(unAlumno.fechaNac.dia);
Write('Mes de nacimiento: ');
LeerMes(unAlumno.fechaNac.mes);
Write('Año de nacimiento: ');
ReadLn(unAlumno.fechaNac.anio);
Write('Calificación: ');
LeerNota(unAlumno.nota)
end; {LeerFicha}
begin
with unAlumno do begin
end; {LeerFicha}
En ciertos casos es conveniente poder variar el tipo y nombre de algunos de los campos existentes
en un registro en función del contenido de uno de ellos. Supongamos, por ejemplo, que en el regis-
tro tFicha definido anteriormente queremos incluir información adicional dependiendo de la nacio-
nalidad. Si es argentina, añadiremos un campo con el D.N.I., y si no lo es, añadiremos un campo
para el país de origen y otro para el número del pasaporte.
Con este objetivo se pueden definir en Pascal los registros con variantes, que constan de dos par-
tes: la primera, llamada parte fija, esta formada por aquellos campos del registro que forman parte
de todos los ejemplares; la segunda parte, llamada parte variable, esta formada por aquellos cam-
pos que solo forman parte de algunos ejemplares.
En la parte fija, debe existir un campo selector mediante el cual se determina la parte variable que
se utilizara. Este campo selector debe ser único, es decir, solo se permite un campo selector.
39
Type...
tNombre = array[1..50] of char;
tDNI = array[1..8] of `0'..'9';
tPais = array[1..20] of char;
tPasaporte = array[1..15] of `0'..'9';
tFicha = record
nombre: tNombre;
fechaNac: tFecha;
nota: tCalificaciones;
case Argentino : Boolean of
True :
(dni : tDNI;
False :
(pais : tPais;
pasaporte : tPasaporte
end; {tFicha}
var
c: char;
begin
with unAlumno do begin
Write('Introduce el nombre del alumno:'); ReadLn(nombre);
with fechaNac do begin
Write('Día de nacimiento: '); ReadLn(dia);
Write('Mes de nacimiento: '); LeerMes(mes);
Write('Añoo de nacimiento: '); ReadLn(anio)
end; {with fechaNac}
case c of
's','S' :
begin {el alumno es argentino}
Argentino:= True;
Write('DNI: '); LeerDNI(dni)
40
Memoria dinámica
Unidad Nº 9 Punteros
Un puntero es una variable que sirve para señalar la posición de la memoria en que se
encuentra otro dato almacenando como valor la dirección de ese dato. Para evitar confusión entre
la variable puntero y la variable a la que apunta (o variable referida) conviene imaginar gráficamen-
te este mecanismo. En la siguiente figura se muestra la variable puntero ap, almacenada en la di-
rección 012345, y la celda de memoria que contiene la variable a la que apunta.
Puesto que no es el contenido real de la variable puntero lo que nos interesa, sino el de la
celda cuya dirección contiene, es más común usar el siguiente diagrama
41
que explica por sí mismo el porqué de llamar puntero a la variable ap.
Este ultimo diagrama muestra que un puntero tiene dos componentes: la dirección de memoria a la
que apunta (contenido del puntero) y el elemento referido (contenido de la celda de memoria cuya
dirección está almacenada en el puntero).
Una variable puntero sólo puede señalar a objetos de un mismo tipo, establecido en la declaración.
por ejemplo, un puntero podría señalar a caracteres, otro a enteros y otro a vectores pero, una vez
que se declara un puntero, sólo podremos usarlo para señalar variables del tipo para el cual ha sido
definido. Esta exigencia permite al compilador mantener la consistencia del sistema de tipos, así
como conocer la cantidad de memoria que debe reservar o liberar para el dato apuntado.
type
tApuntChar = ^char;
var
apCar: tApuntChar
Una variable de tipo puntero ocupa una cantidad de memoria fija, independientemente
del tipo del dato señalado, ya que su valor es la dirección en que reside este. Por otra parte, si bien
el tipo de la variable apCar es tApuntChar, el dato señalado por apCar se denota mediante apCar^,
cuyo tipo es por lo tanto char.
El operador de dirección @
Si empleamos el operador @ delante del nombre de una variable estática automáticamente
obtendremos su dirección de memoria. Por tanto podemos asignar este resultado a un puntero del
mismo tipo que la variable estática y realizar modificaciones sobre la variable estática mediante el
puntero
program PunterosConTipo;
var
PunteroEntero : ^Integer;
Entero : Integer;
begin
PunteroEntero := @Entero; { Obtenemos su dirección y la guardamos en
PunteroEntero }
Entero := 10;
Writeln(Entero); {10}
Writeln(PunteroEntero^); {10}
PunterEnter^ := 12;
Writeln(Entero); {12}
Writeln(PunterEntero^); {12}
end.
La creación y destrucción de variables dinámicas se realiza por medio de los procedimientos prede-
finidos New y Dispose, respectivamente. Así pues, la instrucción
New(apCar);
1. Reserva la memoria para un dato del tipo apropiado (en este caso del tipo char).
2. Coloca la dirección de esta nueva variable en el puntero.
Dispose(apCar);
42
En resumen, una variable dinámica sólo se creará cuando sea necesario (lo que ocasiona la corres-
pondiente ocupación de memoria) y, previsiblemente, se destruirá una vez haya cumplido con su
cometido (con la consiguiente liberación de la misma).
Recuérdese que el dato referido por el puntero apCar se expresa apCar^, que es de tipo
char. Por consiguiente, son validas las instrucciones de asignación, lectura y escritura y demás ope-
raciones legales para los caracteres:
type
tApCaracter = ^char;
var
apCar: tApCaracter;
...
New(apCar);
ReadLn(apCar^); {supongamos que se da la letra 'B'}
apCar^:= Pred(apCar^);
WriteLn(apCar^);
type
tApNumero = ^integer;
var
apNum1, apNum2: tApNumero;
New(apNum1);
New(apNum2);
apNum1^:= 2;
apNum2^:= 4;
apNum2^:= apNum1^ + apNum2^;
apNum1^:= apNum2^ div 2;
43
type
tVector10 = array[1..10] of real;
tApNumero = ^integer;
tApVector10 = ^tVector10;
var
New(apNum1);
New(apNum2);
New(apVect);
apNum1^:= 45;
apNum2^:= 30;
apVect^[1]:= 2;
for i:= 2 to 10 do
apVect^[i]:= apVect^[i-1] * 2;
Sólo las operaciones de comparación (con la igualdad) y asignación están permitidas entre punte-
ros.
apNum1^:= 6;
apNum2^:= 6;
en la que se observa que ambos punteros señalan a la misma dirección, resultando ahora iguales al
compararlos:
apNum1 = apNum2
ApNum1^:= 666;
WriteLn(ApNum2^); {666}
el espacio de memoria reservado inicialmente por el puntero apNum1 sigue situado en la memoria.
Lo adecuado en este caso habría sido liberar ese espacio con Dispose antes de efectuar esa asigna-
ción.
El valor nil
Un modo alternativo para dar valor a un puntero es, simplemente, diciendo que no apunta a ningún
dato. Esto se puede conseguir utilizando la constante predefinida nil.
44
apCar:= nil
Gráficamente, el hecho de que una variable puntero no apunte a nada se representa cruzando su
celda de memoria con una diagonal, o bien mediante el símbolo (prestado de la Electricidad) de
conexión a tierra.
Ahora se presentarán algunas situaciones con estructuras no recursivas en las que los punteros
resultan útiles.
El hecho de que los punteros sean objetos de tipo simple es interesante y permite, entre otras co-
sas:
Esta aplicación adquiere mayor relevancia cuando se consideran registros de gran tamaño en los
que el problema es el elevado costo al ser necesaria la copia de todos sus elementos. Por ejemplo,
en la situación
type
tFicha = record
nombre: ...
direccion: ...
...
end; {tFicha}
var
pers1, pers2: tFicha;
la asignación pers1:= pers2 es altamente costosa si el tipo tFicha tiene grandes dimensiones.
Una forma de economizar ese gasto consiste en usar sólo su posición, haciendo uso de punteros:
var
p1, p2: ^tFicha;
p1:= p2
El truco anterior resulta útil, por ejemplo, cuando hace falta intercambiar variables de gran tamaño.
Si se define el tipo
type
tApFicha = ^tFicha;
El procedimiento siguiente efectúa el intercambio rápidamente, con independencia del tamaño del
tipo de datos fichas:
begin
aux:= p1;
p1:= p2;
p2:= aux
end; {intercambiar}
type
tListaAlumnos = array [1..100] of tFicha;
45
type
tListaAlumnos = array [1..100] of tApFicha;
de este modo la ordenación se realizará de una manera mucho más rápida, debido esencialmente
al tiempo que se ahorra al no tener que copiar literalmente todas las fichas que se cambian de
posición.
Lo mismo que la asignación de objetos no simples, en este caso es el de cambiar el objeto por el
puntero al mismo. A continuación vemos un sencillo ejemplo en el que se aprovecha esta carac-
terística.
Supongamos, por ejemplo, que hemos de definir un programa que, dado un punto del plano, un
ángulo y una distancia, calcule un nuevo punto, alcanzado al recorrer la distancia según el rumbo
dado
Naturalmente, en primer lugar se deben definir los tipos, constantes y variables que se usaran en el
programa: se definirá un tipo tPunto como un registro de dos componentes de tipo real y un tipo
dinámico tApPunto que señalará al tipo punto, además se necesitarán dos variables distancia y
ángulo para leer los valores de la distancia y el ángulo del salto, una de tipo punto para el origen
del salto y otra de tipo tApPunto para el punto de destino.
type
tPunto = record
x, y: real
end; {tPunto}
tApPunto = ^tPunto;
var
El cálculo del punto final se puede realizar mediante la definición (y posterior aplicación) de
una función. Esta tendrá que devolver un punto, formado por dos coordenadas; dado que esto no
es posible, se devolverá un puntero al tipo tPunto (de ahí la necesidad del tipo tApPunto). Dentro
de la función utilizaremos el puntero pPun para generar la variable apuntada y realizar los cálculos,
asignándolos finalmente a Destino para devolver el resultado de la función. La definición de la fun-
ción es absolutamente directa:
46
type
tPunto = record
x, y: real
end {tPunto};
tApPunto = ^tPunto;
var
angulo, distancia: real;
origen: tPunto;
pDestino: tApPunto;
begin
Write('Introduzca la x y la y del punto origen: ');
ReadLn(origen.x, origen.y);
Write('Introduzca el rumbo y la distancia: ');
ReadLn(angulo, distancia);
pDestino:= Destino(origen, angulo, distancia);
WriteLn('El punto de destino es:');
WriteLn('X = ', pDestino^.x:20:10,' Y = ', pDestino^.y:20:10)
end. {Salto}
La idea más importante de este programa estriba en que la función devuelve un valor de tipo pun-
tero, que es un tipo simple, y por lo tanto correcto, como resultado de una función. Sin embargo,
la variable referenciada por el puntero es estructurada, y almacena las dos coordenadas del punto
de destino. Mediante este truco, conseguimos obtener un resultado estructurado de una función.
UnidadNº 10
Una lista es una colección lineal de elementos que se llaman nodos. El término colección lineal debe
entenderse de la siguiente manera: tenemos un primer y un último nodo, de tal manera que a cada
nodo, salvo el último, le corresponde un único sucesor, y a cada nodo, salvo el primero, le corres-
ponde un único predecesor. Se trata, pues, de una estructura de datos cuyos elementos están si-
tuados secuencialmente.
Una definición del tipo listas se puede realizar usando punteros y registros. Para ello consideramos
que cada nodo de la lista es un registro con dos componentes:
1. primera almacena el contenido del nodo de la lista
2. la segunda, un puntero que señala al siguiente elemento de la lista, si este existe, o con el
valor nil en caso de ser el ultimo.
Esencialmente, una lista sería representada como un puntero que señala al principio (o cabeza) de
la lista. La definición del tipo tLista de elementos de tipo tElem se presenta a continuación, junto
con la declaración de una variable del tipo lista:
type
47
tElem = char; {o lo que corresponda}
tLista = ^tNodo;
tNodo = record
contenido: tElem;
siguiente: tLista
end; {tNodo}
var
lista : tLista;
se define tLista como un puntero a un tipo no definido todavía. Esto está permitido en Pascal (su-
puesto que tal tipo es definido posteriormente, pero en el mismo grupo de definiciones) precisa-
mente para poder construir estructuras recursivas.
Inicialización
La primera operación a realizar con una lista enlazada es la inicialización. Esta operación
constituye una lista vacía.
Inserción de elementos
Suponemos que nuestra lista está iniciada con el valor nil. Para introducir un elemento nuevoDato
en ella, habrá que completar la siguiente secuencia de pasos
1. En primer lugar, habría que generar una variable del tipo tNodo, que ha de contener el nuevo
eslabón: esto se hace mediante la sentencia New(lista).
2. Posteriormente, se asigna nuevoDato al campo contenido del nodo recién generado. La forma de
esta asignación dependerá del tipo de datos de la variable nuevoDato.
3. Y por ultimo, hay que anular (con el valor nil) el campo siguiente del nodo para indicar que es el
último de la lista.
Para insertar un nuevoDato al principio de una lista no vacía, lista, se ha de proceder como se
indica
1. Una variable auxiliar listaAux se usa para apuntar al principio de la lista con el fin de no perderla.
Esto es:
listaAux:= lista;
2. Después se asigna memoria a una variable del tipo tNodo (que ha de contener el nuevo elemen-
to) Esto se hace mediante la sentencia New(lista).
48
lista^.siguiente:= listaAux;
A continuación se van a definir algunas de las operaciones relativas a listas. Para empezar, se desa-
rrolla un procedimiento para añadir un elemento al principio de una lista, atendiendo al diseño des-
crito por los pasos anteriores:
listaAux:= lista;
New(lista);
lista^.contenido:= nuevoDato;
lista^.siguiente:= listaAux
end; {AnnadirPrimero}
Eliminación de elementos
A continuación se presenta el procedimiento Eliminar que sirve para eliminar el primer elemento de
una lista no vacía. La idea es bastante simple: sólo hay que actualizar el puntero para que señale al
siguiente elemento de la lista (si existe).
49
El procedimiento EliminarK
En primer lugar, se presenta un procedimiento que elimina el k-ésimo elemento de una lista (que
se supone de longitud mayor que k). En el caso en que k = 1, podemos utilizar el procedimiento
EliminarPrimero desarrollado anteriormente, por lo que en este apartado nos restringimos al caso k
> 1.
Emplearemos un método iterativo para alcanzar el (k-1)-ésimo nodo de la lista, de modo que se irá
avanzando nodo a nodo desde la cabeza (el primer nodo de la lista) hasta alcanzar el (k-1)-ésimo
usando un puntero auxiliar apAux.
Para alcanzar el nodo (k-1)-ésimo, se empezará en el primer nodo, mediante la instrucción :
listaAux:= lista;
for i:= 2 to k - 1 do
apAux:= apAux^.siguiente
Una vez hecho esto, sólo hace falta saltarse el nodo k-ésimo, liberando después la memoria que
ocupa. Con esto, el código en Pascal de este procedimiento podría
ser:
nodoSupr:= apAux^.siguiente;
apAux^.siguiente:= nodoSupr^.siguiente;
Dispose(nodoSupr)
end; {EliminarK}
El procedimiento InsertarK
50
También en este caso es necesario usar un puntero auxiliar para localizar el nodo tras el cual se ha
de insertar el nuevo dato y se volverá a hacer uso del bucle for.
La creación del nuevo nodo y la reasignación de punteros es bastante directa, una vez que apAux
señala al nodo k-ésimo. El código correspondiente en Pascal podría ser:
New(nuevoNodo);
nuevoNodo^.contenido:= nuevoDato;
nuevoNodo^.siguiente:= apAux^.siguiente;
apAux^.siguiente:= nuevoNodo;
Uniendo el bucle, para alcanzar el k-ésimo nodo, con las asignaciones anteriores
se obtiene la implementación completa del procedimiento InsertarK:
En este aparado veremos cómo implementar una lista mediante variables dinámicas y punteros. En
una primera aproximación consideraremos que cada nodo de la lista tiene dos componentes: una
de ellas contiene la información del elemento de la lista, mientras la otra contiene un puntero al
siguiente elemento de la misma. Al utilizar un solo enlace por cada nodo de la lista denominaremos
a esta implementación de enlace simple.
La implementación directa de esta idea nos lleva a la siguiente estructura de datos en Pascal.
TYPE
Posicion = ^Elemento;
Elemento = RECORD
info: <TipoBase>
sig: Posicion
END;
TipoLista = Posicion;
51
En este caso el tipo TipoLista es un puntero al primer elemento de la lista. Haremos que la lista sea
un registro con tres componentes:
TYPE
Posicion = ^Elemento;
Elemento = RECORD
info: <TipoBase>
sig: Posicion
END;
TipoLista = RECORD
longitud: INTEGER;
primero, ultimo: Posicion
END;
Al crear una lista, su longitud será 0 y no estarán definidos ni su primer ni su último elemento.
52
En la función Anterior de nuevo hemos supuesto que la posición pasada como argumento
corresponde con un elemento de la lista. Al implementarla hemos de contemplar dos casos:
En el primer caso, cuando la lista la posición pasada como argumento corresponde al pri-
mer elemento de la lista, devolveremos como anterior al mismo un valor indefinido: NIL;
En el segundo caso, cuando la posición indicada no es la primera, hemos de recorrer la lista
hasta encontrar el elemento apuntado. Para ello utilizaremos una variable auxiliar que irá apuntan-
do a los sucesivos elementos recorridos. Comenzaremos apuntando al primer elemento de la lista y
recorreremos los sucesivos elementos con ayuda de la función Siguiente. Cuando el siguiente ele-
mento al apuntado por aux corresponda a la posición p, aux será el anterior que buscamos.
Para acceder al dato contenido en el nodo en la posición p, basta con devolver el campo
info de ese nodo. Si el tipo base lo permite, la operación anterior puede implementarse como una
función del siguiente modo:
FUNCTION Dato (L: TipoLista; p: Posicion): TipoBase;
BEGIN
Dato := p^.info
END;
FUNCTION Buscar (L: TipoLista; e: TipoBase):Posicion;
VAR
aux: Posicion;
d:TipoBase;
BEGIN
aux := Primero(L);
Dato(L, aux, d);
WHILE ( d <> e) AND (aux <> NIL) DO BEGIN
aux := Siguiente (L, aux);
IF (aux <> NIL) THEN Dato (L, aux, d)
END;
Buscar := aux
END;
53
La operación Buscar devolverá la posición del dato e en la lista L si se encuentra, y NIL en caso
contrario. Para buscar el dato, se recorre la lista desde su primer elemento dado por Primero(L),
mediante un puntero auxiliar. Por cada elemento recorrido se extrae el dato que contiene mediante
la función Dato(L,aux,d). Si el dato coincide con el buscado, se devuelve su posición y si no coinci-
de se pasa al siguiente mediante la operación Siguiente(L,aux). El recorrido finaliza cuando hemos
encontrado el elemento (d=e) o cuando hemos recorrido todos los elementos (aux=NIL).
PROCEDURE Almacenar (VAR L: TipoLista; d: TipoBase );
VAR
aux: Posicion;
BEGIN
new(aux);
aux^.info := d;
aux^.sig := NIL;
IF ListaVacia(L) THEN
L.primero := aux
ELSE
L.ultimo^.sig := aux;
L.ultimo := aux;
L.longitud := L.longitud+1
END;
• Si la lista estaba vacía, el campo L.primero debe apuntar al nuevo nodo añadido,
• en caso contrario, debemos enlazar el último nodo de la lista con el recién creado:
L.ultimo^.sig := aux;
• Al almacenar el elemento al final, el campo L.ultimo pasará a apuntar al nuevo nodo y el campo
L.longitud se incrementará en 1.
BEGIN
new(aux1);
aux1^.info := d;
IF ListaVacia(L) THEN
BEGIN
L.primero := aux1;
L.ultimo := aux1;
aux1^.sig := NIL
END
ELSE
IF p = Primero(L) THEN
BEGIN
aux1^.sig := Primero(L);
L.primero := aux1
END
ELSE
54
L.longitud := L.longitud+1
END;
A la hora de insertar un elemento antes de una posición dada, debemos considerar tres
casos posibles:
1. Que la lista este vacía
2. Que la inserción se realice al principio de la lista
3. Que la inserción se realice en cualquier otra posición de la lista
En los tres casos debemos comenzar por crear el nuevo nodo y almacenar en su campo info la
información pertinente, y debemos finalizar incrementando la longitud de la lista en 1.
1. Inserción en una lista vacía
BEGIN
new(aux1);
aux1^.info := d;
IF ListaVacia(L) THEN
BEGIN
L.primero := aux1;
L.ultimo := aux1;
aux1^.sig := NIL
END
ELSE
IF p = Ultimo(L) THEN
BEGIN
p^.sig := aux1;
L.ultimo := aux1;
aux1^.sig := NIL
END
ELSE
BEGIN { cualquier posición }
aux1^.sig := p^.sig;
p^.sig := aux1
END;
L.longitud := L.longitud+1
55
END;
Para insertar después de una posición dada, de nuevo tenemos que contemplar tres casos:
1. Que la lista este vacía
2. Que queramos insertar después del último
3. Que queramos insertar en cualquier otra posición
Modificar un elemento de la lista cuya posición pasamos como parámetro consiste simple-
mente en asignarle el nuevo valor a su campo info.
BEGIN
IF p = Primero(L) THEN
BEGIN
L.primero := p^.sig;
IF Primero(L) = NIL
THEN
L.ultimo := NIL
END
ELSE
BEGIN
aux := Anterior (L,p);
aux^.sig := Siguiente (L,p);
IF p = Ultimo(L) THEN
L.ultimo := aux
END;
dispose(p);
L.longitud := L.longitud - 1
END;
A la hora de borrar un elemento de la lista debemos contemplar dos casos básicos:
que el elemento a borrar sea el primero o que no. En ambos casos debemos acabar por bo-
rrar el nodo mediante un dispose y disminuir la longitud de la lista en uno.
56
Uno de los problemas que plantea la implementación dinámica mediante simple en-
lace es el coste de insertar y borrar nuevos elementos en la lista. En varios casos es
necesario recorrerla desde el principio para poder acceder al elemento anterior al dado
como parámetro. Además, tan sólo es posible recorrer una lista así enlazada en una so-
la dirección. Para solucionar ambos problemas se puede utilizar una lista doblemente
enlazada. En este caso, cada nodo de la lista apuntará tanto al anterior como al si-
guiente.
TYPE
Posicion = ^Elemento;
Elemento = RECORD
info: <TipoBase>;
ant, sig: Posicion
END;
TipoLista = RECORD
longitud: INTEGER;
primero, ultimo : Posicion
END;
Con esta definición de una lista doblemente enlazada, es necesario modificar algunas
operaciones de la Lista para poder adaptarlas al nuevo tipo de nodo utilizado. En este
apartado queda como ejercicio la justificación de la implementación de las operaciones
mostradas.
BEGIN
new (aux);
aux^.info := d;
IF ListaVacia(L) THEN
BEGIN
L.primero := aux;
L.ultimo := aux;
aux^.sig := NIL;
aux^.ant := NIL
END
ELSE
IF p = Primero(L) THEN
BEGIN
aux^.sig := Primero(L);
L.primero^.ant := aux;
aux^.ant := NIL;
L.primero := aux
END
ELSE
57
BEGIN { cualquier posición }
aux^.ant := Anterior(L,p);
aux^.sig := p;
p^.ant^.sig := aux;
p^.ant := aux
END;
L.longitud := L.longitud+1
END;
BEGIN
new(aux);
aux^.info := d;
IF ListaVacia(L) THEN
BEGIN
aux^.sig := NIL;
aux^.ant := NIL;
L.primero := aux;
L.ultimo := aux
END
ELSE
IF p = Ultimo(L) THEN
BEGIN
aux^.ant := Ultimo(L);
L.ultimo^.sig := aux;
aux^.sig := NIL;
L.ultimo := aux
END
ELSE
BEGIN { cualquier posición }
aux^.ant := p;
aux^.sig := Siguiente (L,p);
p^.sig^.ant := aux;
p^.sig := aux
END;
L.longitud := L.longitud + 1
END;
BEGIN
IF p = Primero(L) THEN BEGIN
L.primero := Siguiente(L,p);
IF Primero(L) = NIL THEN
L.ultimo := NIL
ELSE
L.primero^.ant := NIL
END
ELSE
IF p = Ultimo(L) THEN
BEGIN
aux1 := Anterior (L,p);
L.ultimo := aux1;
aux1^.sig := NIL
END
ELSE
BEGIN { Cualquier posición }
aux1 := Anterior (L,p);
aux2 := Siguiente (L,p);
aux1^.sig :=aux2;
aux2^.ant :=aux1
END;
dispose(p);
L.longitud := L.longitud - 1
END;
Multilista
Imaginemos por ejemplo que estamos tratando con una lista de alumnos en la que dos de
sus campos son los apellidos y el DNI del alumno. Puede interesarnos en ocasiones recorrer los
distintos alumnos por orden de apellidos y en otras ocasiones recorrerlos por orden de DNI.
Se trata de una estructura en la que los distintos elementos se enlazan siguiendo el orden
marcado por varios componentes de los datos. Estrictamente hablando una multilista no es una
estructura lineal, puesto que cada elemento tiene más de un anterior y más de un siguiente. Podr-
íamos decir que una multilista integra varias listas ordenadas, cada una de ellas en función de una
componente o campo distinto de los datos almacenado.
58
Posicion = ^ElemMultList;
ElemMultList = RECORD
info: Alumno;
signom, sigdni, signot: Posicion
END;
Multilista = RECORD
primernom, primerdni, primernot: Posicion
END;
El tipo Pila
Definición y ejemplos
La Pila se caracteriza por ser una estructura de datos en la que el último elemento que
se añade a la estructura es el primero en salir. Este modo de funcionamiento se conoce como
política LIFO (Last In, First Out). En todo momento, el elemento que ocupa el extremo variable
de la pila y su posición se denominan tope. Podemos hacernos una imagen más gráfica, pensando
en una pila de bandejas en una cafetería, una pila de platos en un fregadero, una pila de latas en
un expositor de un supermercado: en cualquiera de estos ejemplos, los elementos se retiran y se
añaden por un mismo extremo. En una pila de platos podríamos intentar retirar uno de los inter-
medios con el consiguiente peligro de derrumbe. Sin embargo, en una estructura de datos de tipo
pila, esto no es posible. El único elemento de la pila que podemos retirar es el situado en el tope de
la misma, y si queremos retirar otro, será necesario previamente haber borrado todos los situados
"por encima" de él.
La mejor solución para implementar una estructura dinámica como es la pila es utilizar
memoria dinámica. Al usar memoria dinámica, no reservamos una zona de memoria para la pila en
tiempo de compilación, sino que vamos reservando espacio adicional conforme lo vamos necesitan-
do durante la ejecución del programa. Para lograr esto la mayor parte de los lenguajes, y entre
ellos el Pascal, nos proporcionan un tipo de datos un tanto especial: el puntero.
Para realizar la implementación de una pila utilizando memoria dinámica debemos tener en
cuenta las siguientes consideraciones:
• Sólo podemos acceder a un elemento de la pila, el tope, es decir, la estructura sólo es visible a
través del puntero a la posición de memoria que ocupa el tope. De acuerdcon esto, la definición de
la pila se identifica con un puntero:
TipoPila = ^ElemPila;
• Es una estructura ordenada, por lo que se necesita un mecanismo que una entre sí los elementos
que la forman sin perder el orden. Según esto, cada elemento será realmente un registro cuyos
campos son:
59
La declaración del tipo pila en Pascal queda como sigue:
TYPE
TipoPila = ^ElemPila;
ElemPila = RECORD
info: TipoBase;
sig: TipoPila
END;
VAR
p: TipoPila;
La operación CrearPila devuelve una variable de tipo TipoPila. En el momento de crear una
pila el valor de su tope está indefinido. Dado que en este caso, la pila se identifica con un puntero
que apunta a su tope, asignamos el valor NIL a este puntero.
FUNCTION PilaVacia (p: TipoPila):BOOLEAN;
BEGIN
PilaVacia := (p=NIL)
END;
La pila estará vacía cuando no contenga ningún elemento, es decir, cuando su tope no
apunte a ningún elemento. Con esta implementación está condición se dará cuando su tope valga
NIL.
Sabemos que la variable de tipo TipoPila, se identifica con un puntero a su tope, es decir,
apunta al nodo de la pila situado en su tope. Así pues, el elemento situado en el tope de la pila será
el contenido en el campo info de este nodo: p^.info. Al tratar de acceder al tope de una pila hemos
de tener en cuenta la posibilidad de que este vacía. Si se produce esta condición, la operación Tope
devuelve el valor verdadero en un argumento de tipo lógico denominado error. Si la pila no está
vacía este argumento tomará el valor falso.
PROCEDURE Apilar ( VAR p: TipoPila; e: TipoBase);
VAR
q: TipoPila;
60
Para apilar un nuevo elemento en la pila, lo hacemos "sobre" su tope, y este nuevo ele-
mento pasa a ser un nuevo tope. Para implementar esta operación recorremos los siguientes pa-
sos:
• En primer lugar reservamos espacio para un nuevo nodo en la pila mediante la operación
new(q). Para poder realizar esta operación hemos definido una variable local auxiliar q de tipo Ti-
poPila que apuntará al nuevo nodo durante su proceso de creación.
• En segundo lugar asignamos el valor al nuevo elemento. Para ello asignamos el valor pasado
como parámetro de entrada al campo que contendrá la información en el nuevo nodo: q^.info :=
e.
• En tercer lugar colocamos el nuevo nodo "sobre" el tope anterior. Para lograr esto, hacemos que
el campo que apunta al siguiente en el nuevo nodo, apunte al tope anterior de la pila: q^.sig := p.
• Finalmente hacemos que el nuevo nodo sea el tope de la pila, y para ello hacemos que la variable
que apunta al tope de la misma, p, apunte al nuevo nodo: p := q.
Así pues, hemos añadido un nuevo elemento a la pila. Este nuevo elemento tiene el valor e
y actúa como nuevo tope de la misma. Dado que q es una variable local al procedimiento Apilar, al
finalizar el mismo se liberará el espacio que ocupa y la pila quedará como:
Para desapilar el elemento que se encuentra en el tope de la pila podríamos pensar que
basta con liberar el espacio que ocupa mediante una operación dispose sobre la variable p. Sin
embargo, si solamente hiciésemos esto, el puntero al tope quedaría con un valor indefinido y per-
deríamos la única referencia que tenemos a los elementos de la pila. Con el fin de evitar este pro-
blema, desarrollamos los siguientes pasos:
• En primer lugar utilizamos una variable local auxiliar q de tipo TipoPila y hacemos que esta apun-
te también al tope de la pila: q: = p.
61
• A continuación hacemos que el tope de la pila pase a apuntar al siguiente elemento de la misma:
p : = p^.sig.
• Finalmente, liberamos el espacio ocupado por el tope actual utilizando para ello la variable auxiliar
q: dispose(q).
La variable auxiliar q quedará con un valor indefinido, pero al finalizar el procedimiento Desapilar,
dado que es una variable local al mismo, se liberará el espacio que ocupa en memoria.
Al igual que ocurre con el procedimiento Tope, al intentar desapilar un elemento de la pila
hemos de considerar la posibilidad de que se encuentre vacía. En este caso devolveremos un valor
verdadero en un argumento adicional error de tipo lógico, mientras que si la pila no está vacía de-
volveremos falso sobre este argumento.
El concepto de cola es ampliamente utilizado en la vida real. Cuando nos situamos ante la taquilla
del cine para obtener nuestra entrada, o cuando esperamos en el autoservicio de un restaurante
solemos hacerlo en una cola. Esto significa que formamos una fila en la que el primero que llega es
el primero en obtener el servicio y salir de la misma. Esta política de funcionamiento se denomina
FIFO (First In First Out), es decir, el primer elemento en entrar es el primer elemento en salir.
En la vida real puede perfectamente ocurrir que alguien pretenda saltarse su turno en una cola, o
incluso que abandone la misma antes de que le toque el turno. Sin embargo, en ambos casos se
está incumpliendo la política de funcionamiento de la cola y, estrictamente hablando, ésta deja de
serlo.
En el ámbito de las estructuras de datos definiremos una cola del siguiente modo:
Definición
Cuando hablamos de un conjunto ordenado, al igual que ocurre con las pilas, nos referimos
a la disposición de sus elementos y no a su valor. Esto es, los elementos no tienen porque estar
ordenados según su valor, sino que cada uno de ellos, salvo el primero y el último, tiene un ante-
rior y un siguiente. Por otro lado, al decir que los elementos de la cola son homogéneos, queremos
decir que son del mismo tipo base, aunque sin establecer ninguna limitación sobre este tipo.
Finalmente decir que en la literatura sobre estructuras de datos la cabeza de una cola suele
denominarse también principio o frente de la misma, y el final suele denominarse fondo. Al igual
que las pilas, las colas se gestionan añadiendo y borrando elementos de las mismas. En este caso
en particular las dos operaciones básicas de manipulación funcionan del siguiente modo:
Al igual que en la definición de la Pila, al utilizar punteros para definir una Cola, se llega a
una definición recursiva:
TYPE
TipoPuntero = ^ElemCola;
ElemCola = RECORD
info: <TipoBase>;
sig: TipoPuntero
END;
62
Gráficamente:
Al crear la cola tanto la cabeza como el final de la misma están indefinidos. Así pues, se asigna el
valor NIL a los punteros asociados.
Para eliminar un elemento hay que hacer que la cabeza de la cola apunte al siguiente elemento de
la misma. A continuación se borra el elemento que ocupa la cabeza en la actualidad. Veamos los
pasos seguidos:
• En primer lugar se usa una variable auxiliar de tipo TipoPuntero para acceder al elemento de la
cola situado en la cabeza:
63
• A continuación se hace que la cabeza pase a apuntar al siguiente elemento, dado por aux^.sig.
• Para añadir un nuevo elemento, las tres primeras instrucciones del procedimiento crean y rellenan
un nuevo nodo apuntado por aux:
64
Ejemplo de cola.
Program ejemplo;
uses crt;
TYPE
TipoPuntero = ^ElemCola;
ElemCola = RECORD
info: integer;
sig: TipoPuntero
END;
TipoCola = RECORD
cabeza, final : TipoPuntero
END;
65
Procedure Mostrar(q: tipocola);
Var
Cont: Integer;
apuntaaux:tipopuntero;
Begin
Cont:= 0;
if (q.final = nil) then
writeln(' cola vacia')
else
begin
apuntaaux:=q.cabeza;
repeat
writeln(apuntaaux^.info);
apuntaaux:=apuntaaux^.sig
until apuntaaux = nil
End;
End;
var
el:integer;
coL:tipocola;
op: char;
error:boolean;
begin
repeat
clrscr;
mostrar(col);
aniadir(col,el);
end;
'2': begin
eliminar(col,error);
end;
'3':begin
mostrar(col);
end;
end;
until op='5';
end.
Definición
Un árbol es una estructura de datos no lineal y homogénea en el que cada elemento puede tener
varios elementos posteriores, pero tan sólo puede tener un elemento anterior. De hecho, podemos
establecer una clasificación jerárquica de todos los tipos de datos que hemos visto, de modo que
unos sean casos particulares de otros. Así, el tipo de estructura más general son los grafos. En un
grafo cada elemento puede tener varios elementos anteriores y varios elementos posteriores. Los
66
Terminología básica:
Asociados al concepto de árbol, existen toda una serie de términos que es necesario conocer para
manejar esta clase de estructura de datos. Supongamos los siguientes ejemplos de árboles:
• Nodo Padre de un nodo N es aquel que apunta al mismo. En un árbol cada nodo sólo puede tener
un padre. En el ejemplo 1, A es el padre de B y C, y a su vez, B es el padre de D.
• Nodo Hijo de otro nodo A es cualquier nodo apuntado por el nodo A. Un nodo puede tener varios
hijos. En el ejemplo 1, B y C son los nodos hijos de A y todos los nodos tienen uno o dos hijos.
• Nodo Raíz es el único del árbol que no tiene padre. En la representación que hemos utilizado, el
nodo raíz es el que se encuentra en la parte superior del árbol: A.
• Hojas son todos los nodos que no tienen hijos. En la representación del ejemplo 1 son hojas los
nodos situados en la parte inferior: D, G, H y F.
• Nodos Interiores son los nodos que no son ni el nodo raíz, ni nodos hoja. En el ejemplo 1, son
nodos interiores B, C y E.
• Camino es una secuencia de nodos, en el que dos nodos consecutivos cualesquiera son padre e
hijo. En el ejemplo 1 A-B-D es un camino, al igual que E-G y C-E-H.
• Rama es un camino desde el nodo raíz a una hoja. En el ejemplo 1, A-C-E-G y A- C-F son ramas.
• Altura es el máximo número de nodos de las ramas del árbol. Dicho en otros términos, el máximo
número de nodos que hay que recorrer para llegar de la raíz a una de las hojas. La altura del árbol
del ejemplo 1 es 4, ya que esa es la longitud de la rama A-C-E-H, que junto a A-C-E-G son las dos
más largas.
• Grado es el número máximo de hijos que tienen los nodos del árbol. Así, en el ejemplo anterior el
árbol es de grado dos. Démonos cuenta de que una lista no es más que un árbol de grado uno, tal
y como podemos ver en los ejemplos 2 y 3.
67
• Nivel de un nodo, es el número de nodos del camino desde la raíz hasta dicho nodo. En el árbol
del ejemplo 1, A tiene nivel 1; B y C tienen nivel 2; D, E y F tienen nivel 3 y G y H tienen nivel 4.
Un tipo especial de árbol que se usa muy a menudo son los árboles binarios
Definición 1
Un Árbol binario es un árbol de grado 2.
Definición 2
El árbol del ejemplo anterior es un árbol binario, ya que cada nodo tiene como máximo dos
hijos. Démonos cuenta que en cualquier árbol, no sólo en los binarios, si eliminamos el nodo raíz,
obtenemos dos árboles. Aquel que colgaba del enlace izquierdo del nodo raíz se denomina subárbol
izquierdo y aquel que colgaba del enlace derecho se denomina subárbol derecho. Además, en un
árbol binario, todos los subárboles son también árboles binarios.
De hecho, a partir de cualquier nodo de un árbol podemos definir un nuevo árbol sin más
que considerarlo como su nodo raíz. Por tanto, cada nodo tiene asociados un subárbol derecho y
uno izquierdo.
Existen algunos tipos especiales de árboles binarios en función de ciertas propiedades. Así
por ejemplo:
• Árbol binario equilibrado es aquel en el que en todos sus nodos se cumple la siguiente propiedad,
altura(subárbol_izquierdo) - altura(subárbol_derecho) | ≤ 1.
Así, el árbol del ejemplo 1 sería un árbol binario equilibrado, mientras el del ejemplo 2 no lo
sería. En el segundo caso el subárbol izquierdo de A tiene una altura 2, mientras su subárbol dere-
cho tiene una altura 0.
• Árbol binario completo es aquel en el que todos los nodos tienen dos hijos y todas las hojas están
en el mismo nivel. Se denomina completo porque cada nodo, excepto las hojas, tiene el máximo de
hijos que puede tener.
En estos árboles se cumple que en el nivel k hay 2k-1 nodos y que, en total, si la altura es
h, entonces hay 2h - 1 nodos.
68
Al igual que ocurre en el caso de las listas, podemos implementar un árbol binario mediante estruc-
turas estáticas o mediante estructuras dinámicas. En ambos casos, cada nodo del árbol contendrá
tres valores:
Gráficamente:
La representación de cada nodo en esta implementación será también un registro de tres campos,
pero en este caso los enlaces serán punteros a los subárboles izquierdo y derecho de cada nodo.
Por lo tanto, la estructura de datos en Pascal para definir un árbol binario será la siguiente:
TYPE
TArbol = ^Nodo;
Nodo = RECORD
info: <tipobase>;
izq, der: TArbol
END;
De este modo, un árbol se identifica con un puntero a su nodo raíz, a través del cual podemos ac-
ceder a sus distintos nodos.
Veamos ahora como se implementarían las distintas operaciones incluidas en el TAD Árbol
Binario usando esta representación dinámica.
69
END;
Consideraremos que un árbol está vacío cuando el puntero a su nodo raíz apunte a NIL.
PROCEDURE ConstArbol (subi, subd: TArbol; d:<tipobase>;
VAR nuevo: TArbol);
BEGIN
new(nuevo);
nuevo^.izq := subi;
nuevo^.der := subd;
nuevo^.info := d
END;
Tal y como hemos definido la operación de construcción de un nuevo árbol, esta se realiza
a partir de valor de tipo base y dos subárboles. Se crea un nuevo nodo al que se le asigna el valor
pasado como argumento. Los dos subárboles pasan a ser el subárbol derecho e izquierdo del nuevo
nodo. El nuevo nodo se convierte en la raíz del árbol recién creado.
PROCEDURE SubIzq (A: TArbol; VAR subi:TArbol);
BEGIN
subi := A^.izq
END;
Para acceder al subárbol izquierdo de un árbol, basta con acceder al puntero al hijo izquier-
do de su nodo raíz.
Recorrer un árbol consiste en acceder una sola vez a todos sus nodos. Esta operación es básica en
el tratamiento de árboles y nos permite, por ejemplo, imprimir toda la información almacenada en
el árbol, o bien eliminar toda esta información o, si tenemos un árbol con tipo base numérico, su-
mar todos los valores...
En el caso de los árboles binarios, el recorrido de sus distintos nodos se debe realizar
en tres pasos:
Imponiendo la restricción de que el subárbol izquierdo se recorre siempre antes que el de-
recho, esta forma de proceder da lugar a tres tipos de recorrido, que se diferencian por el orden en
el que se realizan estos tres pasos. Así distinguimos:
• Preorden: primero se accede a la información del nodo, después al subárbol izquierdo y después
al derecho.
70
Si el nodo del que hablamos es la raíz del árbol, estaremos recorriendo todos sus
nodos. Debemos darnos cuenta de que esta definición del recorrido es claramente recursi-
va, ya que el recorrido de un árbol se basa en el recorrido de sus subárboles izquierdo y
derecho usando el mismo método. Aunque podríamos plantear una implementación itera-
tiva de los algoritmos de recorrido, el uso de la recursión simplifica enormemente esta
operación.
Así pues, utilizando la recursividad, podemos plantear la siguiente implementación
de los tres tipos de recorrido descritos:
71
Árboles binarios de búsqueda
Imaginémonos que queremos encontrar un elemento en una lista ordenada. Para hacerlo debere-
mos recorrer sus elementos desde el primero hasta encontrar el elemento buscado o uno mayor
que este. El coste medio de esta operación involucrará en un caso medio el recorrido y compara-
ción de n/2 nodos, y un coste en el caso peor O(n). Si en lugar de utilizar una lista, estructuramos
la información de modo adecuado en un árbol, podremos reducir el coste de la búsqueda a
O(log2n).
Para hacernos una idea de lo que supone esta reducción del coste, supongamos que que-
remos encontrar un elemento entre 1000. Si almacenamos toda la información en una lista ordena-
da, esta búsqueda puede suponernos recorrer y comparar hasta 1000 nodos. Si esta misma infor-
mación la almacenamos en un árbol binario de búsqueda, el coste máximo será de log2(1000)<10.
Hemos reducido el coste de 1000 a 10 al cambiar la estructura de datos utilizada para almacenar la
información.
Tal y como hemos dicho, no basta con almacenar la información en un árbol para facilitar la
búsqueda, debemos utilizar un tipo especial de árbol: un árbol binario de búsqueda. Si además
queremos que esta búsqueda sea lo más eficiente posible debemos utilizar árboles de búsqueda
binarios equilibrados.
Definición
Un árbol binario de búsqueda es una estructura de datos de tipo árbol binario en
el que para todos sus nodos, el hijo izquierdo, si existe, contiene un valor menorque el nodo padre
y el hijo derecho, si existe, contiene un valor mayor que el del nodo padre.
Obviamente, para establecer un orden entre los elementos del árbol, el tipo base debe ser escalar o
debe tratarse de un tipo compuesto con una componente que actúe como clave de ordenación.
La siguiente figura es un ejemplo de árbol binario de búsqueda conteniendo enteros.
72
Inserción de un elemento
La operación de inserción de un nuevo nodo en un árbol binario de búsqueda consta de tres fases
básicas:
La creación de un nuevo nodo supone simplemente reservar espacio para el registro aso-
ciado y rellenar sus tres campos.
Dado que no nos hemos impuesto la restricción de que el árbol resultante sea equilibrado,
consideraremos que la posición adecuada para insertar el nuevo nodo es la hoja en la cual se man-
tiene el orden del árbol. Insertar el nodo en una hoja supone una operación mucho menos compli-
cada que tener que insertarlo como un nodo interior y modificar la posición de uno o varios sub-
árboles completos.
La inserción del nuevo nodo como una hoja supone simplemente modificar uno de los enla-
ces del nodo que será su padre.
Veamos con un ejemplo la evolución de un árbol conforme vamos insertando nodos siguiendo el
criterio anterior respecto a la posición adecuada.
73
El siguiente código implementa de modo iterativo la operación de inserción de un nodo siguiendo la
descripción anterior.
En el algoritmo podemos diferenciar claramente las tres fases. Para acabar de entenderlo
son necesarios algunos comentarios sobre su código.
En primer lugar, si el árbol pasado como argumento está vacío, el árbol resultante tan sólo
contiene el nuevo nodo creado, es decir, es un puntero al mismo.
Para buscar la posición adecuada donde insertar el nuevo nodo recorremos el árbol desde
su raíz hasta encontrar su posición como hoja que mantenga el orden. Para este recorrido maneja-
74
Eliminación de un elemento
b. Si el nodo a borrar tiene un solo hijo, se añade como hijo de su padre, sustituyendo la posición
ocupada por el nodo borrado.
c. Si el nodo a borrar tiene los dos hijos se siguen los siguientes pasos:
i. Se busca el máximo de la rama izquierda o el mínimo de la rama derecha.
ii. Se sustituye el nodo a borrar por el nodo encontrado.
BEGIN
75
{ Busqueda del elemento a eliminar }
p := A;
enc := false;
En el procedimiento anterior podemos diferenciar claramente los dos pasos básicos de que
consta la eliminación de un nodo. En el primer paso, para buscar el nodo que queremos eliminar,
utilizamos dos punteros: un puntero p que apunta al nodo cuyo contenido estamos comprobando y
otro p_padre que apunta a su nodo padre. Este segundo puntero nos permitirá mantener la co-
nexión dentro del árbol una vez eliminado el nodo. Si salimos del WHILE por la condición de Arbol-
Vacio, significa que no hemos encontrado el nodo a eliminar, y en ese caso no pasamos a la se-
gunda fase del algoritmo.
Durante la fase de eliminación del nodo hemos diferenciado los distintos casos. Utilizamos
un puntero auxiliar sust que apuntará al nodo sustituto del eliminado, es decir, a aquel que ocupará
su posición. Si el subárbol izquierdo del nodo a eliminar está vacío, el sustituto será su hijo dere-
cho, mientras que si el subárbol derecho del nodo a eliminar está vacío, el sustituto será su hijo
izquierdo. Si no se cumple ninguna de las dos condiciones anteriores, el nodo a sustituir tiene no-
dos en sus dos subárboles y su eliminación será más compleja. En todo caso, al finalizar el algorit-
mo, hemos de enlazar el padre del nodo eliminado con el nodo sustituto y liberar la memoria ocu-
pada por el nodo suprimido. Para ello se usa el siguiente código:
IF p=A THEN
A := sust
ELSE IF (p = SubIzq(p_padre)) THEN
p_padre^.izq := sust
ELSE
p_padre^.der := sust;
dispose(p)
Veamos ahora el caso en el que el nodo a eliminar tiene nodos en sus dos subárboles (caso
c). En esta situación, elegiremos como sustituto al nodo con mayor valor de su subárbol izquierdo.
Este nodo será el situado más a la derecha de este subárbol. Para buscarlo, comenzaremos por
desplazarnos al hijo izquierdo del nodo a eliminar y a partir de este punto nos desplazaremos siem-
76
p_sust := p;
sust := SubIzq(p);
WHILE NOT ArbolVacio(SubDer(sust)) DO
BEGIN
sust := SubDer(sust);
p_sust := sust
END;
Como vemos en el código anterior, mantenemos un puntero al nodo sustituto, sust, y un puntero a
su padre, p_sust. Veamos gráficamente un par de ejemplos de cómo quedarían los distintos punte-
ros auxiliares en este caso.
Una vez localizados tanto el nodo a eliminar y su nodo padre, como el nodo sustituto y su nodo
padre, podemos ya realizar la sustitución. Para ello comenzaremos por salvaguardar el posible sub-
árbol izquierdo del nodo sustituto. Por la forma en la que lo hemos encontrado, no tendrá subárbol
derecho. En los ejemplos anteriores, el subárbol a salvaguardar estará formado por el nodo 6 en el
ejemplo 1 y por el nodo 9 en el ejemplo 2.
IF p_sust = p THEN
p_sust^.izq := SubIzq(sust)
ELSE
p_sust^.der := SubIzq(sust);
Para finalizar la sustitución, deberemos colgar del nodo sustituto los subárboles del nodo eliminado,
tal y como se ve en el paso 2 de la figura anterior. Esto se hace con elsiguiente código.
sust^.izq := SubIzq(p);
sust^.der := SubDer(p);
Como hemos comentado anteriormente, los últimos pasos llevados a cabo por el algoritmo son el
enlace del nodo padre del eliminado con el nodo sustituto, y la liberación del espacio ocupado por
el nodo eliminando. Estas operaciones constituyen el paso 3 de la figura anterior. Si lo redibujamos
de modo más adecuado, el árbol resultante tras eliminar el nodo 11 quedará del siguiente modo:
77
Unidad CRT
Las constantes definidas de Turbo Pascal para indicar el modo de pantalla que se utilizará son:
Colores
Parpadeo
Blink 128
La unidad CRT provee un conjunto de variables utilizadas para modificar aspectos referentes a en-
tradas de teclado, modo de la pantalla, etc. A continuación se listan las principales con su función
específica.
CheckBreak
Tipo : boolean.
DirectVideo
Tipo : boolean.
Cuando existen problemas de entrada/salida de texto se debe desactivar esta variable (guardar en
ella el valor false) que inhibe la escritura directa de caracteres a la memoria de video.
LastMode
Tipo : Word
La variable LastMode contiene el valor del modo de texto actual. Se inicializa al momento de iniciar
el programa y se utiliza comunmente para restaurar el modo original al momento de terminar el
programa.
TextAttr
Tipo : Byte
Usualmente se utiliza para cambiar los atributos de colores en la pantalla, es más rápido que los
procedimientos TextColor y TextBackground que tienen la misma función.
WindMin
Tipo : Word; Esta variable contiene las coordenadas de la esquina superior izquierda de la ventana
de texto activa definida por el procedimiento Window.
WindMax
78
A continuación se listan algunos de los procedimientos que incluye esta unidad con una breve des-
cripción de cada uno.
ClrEol
Sintaxis:
ClrEol;
Este procedimiento borra todos los caracteres de la línea actual desde la posición del cursor hasta
el final de la linea. Ejemplo:
PROGRAM Proc_ClrEol;
USES Crt;
VAR x,y : Integer;
BEGIN
FOR x := 1 TO 24 DO
FOR y := 1 TO 80 DO
Write('#');
GotoXY(15,15);
ClrEol;
END.
ClrScr
Sintaxis:
ClrScr;
Se utiliza para borrar la pantalla completa o la ventana actual y situa el cursor en la esquina supe-
rior izquierda. Ejemplo:
PROGRAM LimpiarPantalla;
USES Crt;
VAR x,y : Integer;
Prb : String;
BEGIN
FOR x := 1 TO 24 DO
FOR y := 1 TO 80 DO
Write('#');
WriteLn('Presione [ENTER] para borrar la pantalla');
ReadLn(Prb);
ClrScr;
WriteLn('Se borró la pantalla');
END.
Delay
Sintaxis:
Delay(Tmp : Word);
Detiene la ejecución del programa durante un tiempo especificado en Tmp en milisegundos. El
intervalo válido es desde 0 hasta 65535, la precisión del retardo depende de la precisión del reloj
interno de la computadora. Ejemplo:
PROGRAM Retardo;
USES Crt;
BEGIN
WriteLn('Inicia retardo de aproximadamente 5 segundos');
Delay(5000);
WriteLn('Fin del retardo');
END.
DelLine
Sintaxis:
DelLine;
Borra la linea donde se encuentra el cursor y las lineas inferiores suben una posición. Ejemplo
79
PROGRAM BorrarLinea;
USES Crt;
VAR x : Integer;
prb : String;
BEGIN
ClrScr;
For x := 1 to 20 DO
WriteLn('Linea número: ', x);
WriteLn('Presione [ENTER] para borrar la linea 6');
GotoXY(0,6);
DelLine;
WriteLn('Linea 6 eliminada');
END.
GotoXY
Sintaxis:
GotoXY(x, y : Byte);
Posiciona el cursor en las coordenadas especificadas por x y y.
El byte x representa la columna partiendo de izquierda a derecha y el byte y represen-
ta la fila partiendo de arriba hacia abajo. Ejemplo:
PROGRAM Posición;
USES Crt;
BEGIN
GotoXY(10,10);
Write('*');
GotoXY(20,20);
Write('*');
Readkey;
END.
InsLine
Sintaxis:
InsLine
Inserta una línea en blanco en la posición actual del cursor.
NoSound
Sintaxis:
NoSound;
Desactiva el sonido iniciado con el procedimiento Sound.
Sound
Sintaxis:
Sound (Frecuencia : Word);
Genera un sonido en la bocina de la computadora a una frecuencia determinada por el valor de
Frecuencia. Para detener el sonido es necesario ejecutar el procedimiento NoSound.
TextBackground
Sintaxis:
TextBackGround (Color : byte);
Se utiliza para seleccionar el color del fondo de la pantalla. Los valores que puede utilizar son del 0
al 7, también es posible utilizar las constantes predefinidas para los colores.
TextColor
Sintaxis:
TextColor (Color : Byte);
El procedimiento TextColor se usa para seleccionar el color del texto en la pantalla.
TextMode
Sintaxis:
TextMode (Modo : Word);
Define el modo de video, ésto es, el número de filas y columnas que se podrán mostrar en pantalla,
también si se mostrarán en blanco y negro o en color.
80
Window
Sintaxis:
Window (x1, y1, x2, y2 : Byte);
Define las coordenadas de la ventana de texto activa; x1 y y1 son las coordenadas de la esquina
superior izquierda, x2 y y2 son las coordenadas de la esquina inferior derecha.
KeyPressed
Tipo: Boolean
La función KeyPressed devuelve el valor de True si se pulsó alguna tecla y false si no se ha presio-
nado ninguna.
ReadKey
Tipo: Char
Esta función lee un caracter del teclado, se utiliza mucho para leer teclas de dirección, teclas de
control y de funciones.
WhereX
Tipo: Byte
WhereY
Tipo: Byte
La función WhereY devuelve el número de la fila donde se encuentra el cursor al momento de lla-
mar a la funcioacute;n.
Unidad Nº 14 Archivos
Lectura: En este caso, el archivo que queremos leer DEBE existir y para abrirlo utilizamos
la instrucción Reset(textfile).
Escritura: En este caso el archivo puede existir o no. Si no existe necesariamente lo de-
bemos crear, pero si existe podemos abrirlo, para continuar escribiendo en él o borrarlo y
empezar de cero con un archivo nuevo. La salida debe ser generada secuencialmente y
nunca podemos volver atrás en lo que ya escribimos en el archivo. La salida debe ser
siempre abierta como un archivo nuevo y de escritura. Para ello utilizamos la instrucción
ReWrite(textfile). Esta instrucción crea un archivo nuevo.
81
Las variables de tipo Text se asocian con el nombre del archivo al cual están destinadas
con la instrucción Assign(textfile, filename). Es necesario decirle al compilador qué archivo
queremos abrir antes de abrirlo.
Para leer y escribir se usan las instrucciones Read(textfile, vars ....) y Write(textfile,
vars...), respectivamente. Existen otras dos funciones ReadLn y WriteLn que tiene el mis-
mo efecto que las anteriores pero leen hasta un fin de línea y escriben un fin de línea,
respectivamente.
Var
F: Text; // Esta será nuestra variable para manejar archivos
a, b: LongInt; // Algunas variabes para leer y escribir
s: String;
Begin
// Excribimos a un archivo
Assign(F, 'c:\ejemplo\test.txt');// En directorio ejemplo.
ReWrite(F);
a := 314; b := 278;
WriteLn(F, a, ' ', b);
WriteLn(F, 'pi = ', a);
Close(F); // Es muy importante CERRAR el archivo que abrimos, para que
los datos se escriban en el disco.
a = 278
b = 314
s = pi = 314
Trabajo Práctico Nº 1
a. A+B = C;
b. y = SUMA;
c. x:=*B;
d. Program Uno,
e. Const x:=18;
2. ¿Cuál es el valor de la variable SUMA después que el siguiente segmento de código es ejecuta-
do?
Program pp;
Var a,b,c,suma:integer;
begin
A:=1;
B:=7;
c:=B div A;
SUMA:=A+C;
Readln;
End.
3. Para cada uno de los segmentos de código siguientes indique lo que sucede a medida que las
sentencias se ejecutan.
82
Program celsiusaFahrenheit;
var
c,f : real; Begin
c:=20
f:= (9/5)*c + 32.0 ; Writeln (f);
End.
Indicar
15 div 12 15 mod 12
24 div 12 24 mod 12
7.- Escribir un programa que lea un entero, lo multiplique por 2 y a continuación lo escriba de nue-
vo por pantalla.
8.- Escribir un programa que convierta un número dado en segundos en el equivalente de minutos
y segundos.
9.- Escribir las sentencias de asignación que permitan intercambiar los contenidos (valores) de dos
variables.
10.- Escribir un programa que lea dos enteros en las variables X e Y, y a continuación obtenga los
valores de:
a) x div y
b) x mod y
Ejecute el programa varias veces con diferentes pares de enteros como entrada.
Estructuras de control
Trabajo practico
1. La municipalidad debe liquidar impuestos atrasados de los últimos 10 años, los datos a ingresar
son: Nro. De Contribuyente, Año, e Importe.
83
Desarrollar un algoritmo que ingrese los datos e imprima la liquidación actualizando el importe de
acuerdo al índice de la siguiente tabla.
1992-1994 :3150
1995 :3000
1996-1997 :2800
1998-2004 :2001
2005:2010 :2000
2.- Leer una temperatura e imprimir el deporte apropiado de acuerdo a la siguiente tabla:
7.- Escribir las tablas de multiplicar del número 1 al número 9 de los primeros 15 números.
9.- Una empresa fabrica tapas de material laminado en 3 formatos: redondo, cuadrado o
rectangular. Cobra $9 el metro cuadrado y si la tapa es redonda, le suma $4 más al total. Se pide:
a) Ingresar el código de forma: 1-redonda, 2- cuadrada, 3- rectangular
b) Ingresar la longitud en metros: si es cuadrada, se ingresa un solo valor y si es redonda,
corresponde al radio del circulo
c) Informar el costo total de la tapa
10.- Se ingresan pares de valores reales y se debe informar el promedio de cada par. El ingreso de
datos finaliza cuando el operador responde NO a la siguiente pregunta: “ Desea calcular el prome-
dio? (SI / NO)?”
11.- Se han anotado las temperaturas máximas y mínimas de cada uno de los días del mes de mar-
zo. Determinar e imprimir el promedio de las temperaturas máximas, cuántos días la temperatura
superó los 30 grados y cuantos días la diferencia entre la temperatura máxima y mínima fue supe-
rior a los 15 grados.
12.- Se realiza una encuesta para estimar el grado de aceptación de los productos x e y en el mer-
cado. A cada encuestado se le pregunta si consume el producto x y si consume el producto y. La
respuesta puede ser si o no. Se pide calcular e informar el porcentaje de consumidores de:
a) del producto x
b) del producto y
c) del producto x solamente
d) del producto y solamente
e) de ambos productos
f) de ninguno de los productos
13.- En una empresa el sueldo se calcula adicionando al básico 50% del mismo, en caso en que la
antigüedad sea superior a los 10 años. Diseñar un algoritmo que lea el nombre del empleado, el
sueldo básico y la antigüedad y escriba el nombre y el sueldo a cobrar.
14.- Leer la ganancia anual y se pide calcular y escribir el impuesto de acuerdo a la siguiente tabla:
Ganancia >= 10000 Impuesto =0
10000 <Ganancia <=1500 Impuesto= 50 + 2% de Ganancia
1500 < Ganancia Impuesto = 300 + 5% de Ganancia.
15.- Ingresar 3 números, donde los dos primeros representan los extremos de un intervalo. Se
solicita verificar si el valor pertenece o no al intervalo.
84
Program Valor;
Uses
crt;
Var
a,b,c, máximo: integer;
Begin
Clrscr;
Writeln(‘ ingrese el valor de A ‘ ); Readln (a);
Writeln(‘ ingrese el valor de B ‘ ); Readln (B);
Writeln(‘ ingrese el valor de C ‘ ); Readln (c);
Maximo:=a;
If (b>=maximo) then
Maximo:=B;
If (c>= máximo) then
Maximo:=c;
Writeln (‘ el elemento máximo es : ‘ , maximo);
Readkey;
End.
18.- Calcular el promedio de los números positivos y negativos por separado, de un conjunto de
datos que se ingresan por teclado, preguntando antes de cada ingreso si hay mas información.
19.- Escribir un programa para convertir una medida dada en pies a sus equivalentes en
a) yardas; b) pulgadas; c) centímetros; d) metros. Un pie = 12 pulgadas, 1 yarda =3 pies, 1 pulga-
da = 2.54 cm, 1 m = 100 cm). Leer el número de pies e imprimir el número de yardas, pies, pulga-
das, centímetros y metros.
20.- Una empresa fabrica dos productos A y B y desea saber cuál de los dos es el mas aceptado en
el mercado. Para ello se realiza una encuesta y por cada persona interrogada se obtiene un par de
valores: el primer valor del par indica la aceptación o no del producto A según sea 1 ó 0.
El segundo valor indica lo mismo referido al producto B. Se pide:
• Total de consumidores encuestados
• Porcentaje de consumidores que aceptan el producto A
• Porcentaje de consumidores que aceptan el producto B
• Porcentaje de consumidores que aceptan el producto A
• Porcentaje de consumidores que aceptan el producto A y B
• Porcentaje de consumidores que aceptan el producto A y NO B.
• Porcentaje de consumidores que aceptan el producto B y NO A.
• Porcentaje de consumidores que NO aceptan el producto A y B
• A= -1 indica fin de datos.
21.- Escribir un programa que lea la hora de un día de notación de 24 horas y la respuesta en no-
tación 12 horas. Por ejemplo, si la entrada es 13:45, la salida será 1:45 PM
22.- Escribir un programa que acepte un año escrito en cifras arábigas y visualice el año escrito en
números romanos, dentro del rango 1000 a 2100.
85
IV= 4, XL= 40, CM=900, MCM= 1900, MCML= 1950, MCMLXXXIX= 1989
23.- Diseñar un algoritmo que construya las facturas de electricidad correspondientes a un bimes-
tre. Por cada usuario se lee nombre y domicilio y los estados del medidor anterior y actual (el fin de
datos viene dado por un ‘fin’). Las facturas deben contener la siguiente información con títulos
aclaratorios:
• Nombre y domicilio:
• Estado del medidor actual:
• Estado del medidor anterior:
• Consumo del bimestre:
• Importe a Pagar:
El importe se calcula en función del consumo de las siguiente forma:
Importe = 1.5 * consumo si consumo <= 100 kwh.
Importe =2.0 * consumo si 100 kwh < consumo <= 200 kwh.
Importe = 2.5 * consumo si consumo >200 kwh.
24.- El equipo de Hockey ha tenido una buena campaña y desea premiar a sus jugadores con un
aumento de salario para la siguiente campaña. Los sueldos deben ajustarse de la siguiente forma:
Sueldo Actual Aumento
0-1800 20%
2001 – 2600 5%
El equipo tiene un cuadro de 20 jugadores. Diseñe un algoritmo que lea el Nombre del Jugador y el
salario Actual y que a continuación imprima el nombre, el sueldo actual y el monto aumentado. Al
final de la lista debe proporcionar, también, el monto total de la nueva nómina que incluye los au-
mentos mencionados.
26.- Dado un conjunto de triplas de datos. Donde el Primer elemento representa el Id_Articulo,
el segundo: Cantidad_Unidades, y el tercero: Total_Pagado. Se pide producir un listado
Id_articulo y Precio_Ariculo_unitario. Por último la cantidad total de unidades vendidas (sin distin-
ción de artículo) y el importe total. El proceso se detendrá cuando el Id_articulo sea igual a cero.
**********
*Algoritmos*
**********
2. Escribir un programa que lea una frase, sustituir todas las secuencias de dos o varios blancos por
un solo blanco y visualizar la frase obtenida.
3. Escribir un programa que lea una frase y a continuación visualice cada palabra de la frase en una
columna, seguido del número de letras que componen la frase.
4. Escribir una función lógica llamada Digito que determine si un carácter es uno de los dígitos de 0
a 9.
5. Escribir una función lógica llamada Vocal que determine si un carácter es una vocal.
6. Escribir un programa que permita al usuario elegir el cálculo del área de cualquiera de las figuras
geométricas: círculo, cuadrado, rectángulo o triángulo mediante funciones.
7. Escribir una función que tenga un argumento de tipo entero y que devuelva la letra P si el núme-
ro es positivo, y la letra N si es cero o negativo.
86
Program EjercicioPalabras;
Uses
crt;
var
palabra : string;
cont : integer;
pal : boolean;
begin
clrscr;
readln(palabra);
cont:=1;
while cont<=length(palabra) div 2 do {Ciclo hasta la mitad de la cadena}
begin
if palabra[cont]=palabra[length(palabra)-(cont-1)] then
begin
pal:=true;
end
else
begin
pal:=false;
Program gl;
Var
A,B,C:integer;
Procedure ejemplo;
Begin
Writeln(‘ Los valores son ‘ , a:3, b:3, c:3)
End;
Procedure cam;
Begin
A:=4;
B:=5;
C:=6;
End;
A:=1;
B:=2;
C:=3;
Ejemplo;
Cam;
Ejemplo;
Readkey;
End.
Modificar el procedimiento Cam declarando a,b,c como variables locales, ¿ cómo cambia la salida si
se ejecutan las modificaciones?
87
program ejemplo;
uses
crt;
var
b:real;
12.-Escribir un programa mediante un procedimiento que acepte un número de dia, mes y año y lo
visualice en el formato
Dd/mm/aa
Por ejemplo, los valores 8, 10, y 2001 se visualizan como:
8/10/01
13.- Escribir una función que tenga un argumento de tipo entero y devuelva la letra P si el número
es positivo, y la letra N si es cero o negativo.
14.- Escribir una función lógica de dos argumentos enteros, que devuelva true si uno divide al otro
y false en caso contrario.
15.- Escribir una función inversa que recibe una cadena cad como parámetro y devuelve los carac-
teres de cad en orden inverso. Por ejemplo, si cad es ‘Pablo?’, la función devuelve ‘?olbaP’.
- P(N) arreglo de proveedores, donde cada P(I) es el nombre del proveedor ordenado alfabética-
mente
- C(N) arreglo de ciudad, donde cada C(I) es el nombre de la ciudad en la que reside el proveedor
P(I)
- A(N) arreglo de artículos, donde cada A(I) es el número de artículos diferentes del proveedor P(I)
Realice un programa en pascal que pueda llevar a cabo las siguientes transacciones:
Dado el nombre del proveedor, informar el nombre de la ciudad en la que reside y el número
de artículos que provee. Actualizar el nombre de la ciudad, en caso de que un proveedor cambie
de domicilio; los datos serán el nombre del proveedor y el nombre de la ciudad a la que se
mudó. Actualizar el número de artículos de un proveedor en caso de que éste aumente o disminu-
ya.
La compañia da de baja a un proveedor: actualizar los arreglos.
88
Se pide:
(1) Crear una estructura bidimensional que almacene el promedio por materia de cada alumno e
informarla asignándole en la impresión un guión al caso de falta de datos mencionado.
(2) Informar el porcentaje de alumnos que cursó cada materia y el p romedio general por materia
considerando los alumnos que la cursaron.
(3) Informar la cantidad de materias que cursó cada alumno y el promedio que obtuvo conside-
rando las materias que cursó.
5) Realizar un programa que implemente el juego del BUSCAMINAS. Dicho juego consiste en lo
siguiente: Existe una matriz bidimensional de NxM en la que se sitúan aleatóriamente K minas. Una
vez distribuidas las minas en el tablero, el jugador especifica una casilla de la tabla, de manera que,
si en dicha casilla existe una mina, el juego termina. Si en la casilla no existe mina, el programa
debe devolver el número de minas que se encuentran en las casillas adyacentes a la casilla en
cuestión, entendiendo por adyacentes todas aquellas casillas que se encuentren encima, debajo, a
la izquierda, a la derecha, y en las cuatro esquinas. El juego se gana cuando el jugador es capaz de
levantar todas las casillas libres del tablero sin haber “explotado” con ninguna mina.
a.- Asignarle valores a todos sus elementos teniendo en cuenta que cada elemento a[i,j] está defi-
nido como
0 3 -2 5
3 4 5 6
7.- Leer una matriz de NxN elementos enteros llamada tierra. Generar un vector llamado agua
que contenga los elementos de la matriz tierra remplazados en la siguiente función: X2*Pi+ 6X3.
89
Calcular el promedio de los elementos que se encuentran en la diagonal superior y generar un vec-
tor llamado Aire tal que contenga los elementos de tierra multiplicados por aire. Y Generar un vec-
tor llamado Fuego de tipo string que contenga los elementos de la diagonal inferior convertidos en
cadena de caracteres. Mostrar por pantalla Agua, fuego, tierra y aire.
8.- Ingresar una matriz A(10,8), calcular e informar la suma de sus elementos.
1.- Diseñar un programa que posea una función recursiva que permita multiplicar dos números
enteros M y N. Acumulando su contenido hasta que uno de los dos llegue a 0.
2.- Diseñar un programa que dado un numero decimal lo transforme a uno binario y a otro Hexa-
decimal.
3.- Escribir las funciones, una recursiva y otra no recursiva, tal que dado el entero positivo X de-
vuelva true(verdadero) si y solo si X es potencia de 2.
Procedure p1 ( a: integer );
begin
if a > 0 then
begin
writeln( a );
p1( a - 1 );
end
else
writeln ( 'Fin' )
end;
¿Qué cambiaría al añadir estas dos líneas después de la instrucción `else ...'?:
writeln ( a );
writeln ( 'Fin de verdad' )
Procedure p3 ( a, b: integer );
begin
if a > 0 then p3 ( a - 1, b + a )
else writeln ( b )
end;
90
12.- Diseñe una función recursiva que devuelva cierto si una palabra es palíndroma o falso en caso
contrario. Decimos que una palabra es palíndroma si se lee igual de derecha a izquierda
que de izquierda a derecha.
1.- Escribir una declaración de tipo registro que almacene la siguiente información sobre un disco
de audio: Título, autor, año de publicación y duración (en segundos).
2.- Escribir una declaración de tipo registro que almacene la siguiente información sobre un auto-
móvil: marca, modelo, año, color, número de puertas, número de cilindros, precio de compra, po-
tencia de caballos.
3.- un programa incluye
Type
Descripción= string [20];
Película = record
Titulo, distribuidor: descripción;
Numcopias :0..100;
Precio :real;
End;
Var
Epica, cartoon: película;
Novelas, Biografia: libro;
I, numcaracteres: integer;
Indicar si la ejecución de las siguientes sentencias son válidas. En caso negativo, indi-
car respuesta
a) Epica.numcopias:=80;
b) Read(biografia);
c) For i:= 1 to numcaracteres do
Read(distribuidos[i]);
d) Read(novelas.editor);
Biografia.titulo := cartoon.titulo;
e) With cartoon do
f) Writeln(costo);
g) With biografia, epica o
Write (precio);
4.- Definir un tipo adecuado para representar información sobre empleados. Debe almacenarse:
a) apellido y nombre del empleado
b) sexo
c) edad (entre 18 y 65 años)
d) estudios cursados (primarios, secundarios, terciarios)
e) estado civil (casado, soltero, viudo, divorciado)
5.-Escribir un procedimiento para leer y validar los datos de un empleado de acuerdo a la defini-
ción del ejercicio anterior.
6) Definir un tipo adecuado para almacenar información sobre autos, debe almacenarse
a) nombre de la empresa fabricante
b) número de modelo (entre 1980 y la actualidad)
c) número de patente (3 letras mayúsculas y 3 dígitos)
d) estado del auto (excelente, bueno, regular, malo)
91
7) Escribir un procedimiento para leer y validar los datos de un auto de acuerdo a la definición del
ejercicio anterior.
8)Definir un tipo adecuado para almacenar información sobre libros. Se debe guardar:
a) ISBN
b) título del libro
c) cantidad y nombre de los autores (máximo 5)
d) cantidad de páginas
e) año de edición (de 1900 en adelante)
f) idioma en que está escrito (español, francés, portugués, inglés, alemán)
9)Escribir un procedimiento para leer y validar los datos de un libro de acuerdo a la definición del
ejercicio anterior.
b)
Program prueba;
Type
Estudiante = record
Letra:char;
Edad:integer;
End;
Puntestu: ^estudiante;
Var
P1,p2:puntestu;
Begin
New(p1);
P1^.edad:=1;
P1^Letra:= ‘A’;
Writeln(p1^.edad, p1^.letra);
New(p2);
P2^.edad:=2;
P2^.letra:= ‘B’;
Writeln (p2^.edad, p2^.letra);
P1:=p2;
P2^.edad:=3;
P2^.edad:=3;
P2^.letra:=’C’;
Writeln(p1^.edad, P1^.letra, p2^.edad, p2^.letra);
Readln;
End.
2.- Complete los tipos de datos apropiados para que sea correcta la siguiente instrucción:
92
WriteLn(p);
WriteLn(p^);
Dispose(p)
(c) ¿De qué forma se pueden recuperar los valores 1..N - 1 generados en el bucle for?
2. Un alumno guarda en un montón los boletines de apuntes de las 7 asignaturas en que se en-
cuentra matriculado (codificadas de 1 a 7). En cada momento tan sólo puede acceder al boletín
situado en la parte superior del montón. Por cada boletín conoce la asignatura, el tema, el número
de páginas y su precio.
a) Definir la estructura de datos más adecuada para guardar la información sobre los bole-
tines.
b) El alumno quiere clasificar los boletines por asignatura en montones separados.
b.1) Definir la estructura de datos más adecuada para guardar la información sobre
losboletines clasificados.
b.2) Implementar un algoritmo que dado el montón original con los boletines de las
7 asignaturas, nos devuelva los boletines clasificados en montones separados.
c) Implementar un algoritmo que, dados los boletines clasificados, calcule la asignatura en
cuyos apuntes el alumno se ha gastado más dinero.
2. Una agencia de viajes ofrece n destinos; para cada destino se puede optar por 5 clases de viaje (
super, luxe, normal, turista y estudiante), y además se ofrecen tres tipos de alojamiento: AD (alo-
jamiento y desayuno), MP (media pensión) y PC (pensión completa).
Cada programa de viaje se caracteriza por la información (destino, clase, alojamiento). Por
cada programa de viaje se quiere saber el número de plazas disponibles, de manera que cuando un
cliente contrata un determinado programa, el número de plazas disponibles se decrementa. Cuando
un programa no dispone de plazas, entonces la información del cliente (nombre, dirección y NIF) se
almacena en orden cronológico. Así, cuando se disponga de nuevas plazas en ese programa se
atenderán las peticiones en orden.
Se desea informatizar la gestión de esta agencia. Entre otras cosas, es preciso:
a) La definición de la estructura de datos que soporte la información descrita (es decir, la
estructura que permita almacenar todos los programas de viaje de la agencia).
93
b) Escribir un algoritmo que, dado un cliente y un determinado programa de viajes, com-
pruebe si es posible o no que el cliente lo contrate. En cualquier caso, habrá que realizar las accio-
nes oportunas para que la estructura se actualice de forma conveniente.
c) Escribir un algoritmo que indique todos los destinos con alguna plaza disponible. La solu-
ción debe incluir la definición de la estructura de datos más idónea para devolver la información,
sabiendo que n < 100.
4. En un supermercado hay 20 cajas registradoras, en cada una de las cuales se colocan los clien-
tes con sus carros de la compra en orden de llegada. Por cada caja registradora queremos guardar
el nombre de la cajera, la recaudación acumulada y los carros en espera.Por otro lado, en cada
carro se amontonan los distintos productos, de modo que tan sólo puede añadirse o extraerse el
situado en la parte superior. Por cada producto guardamos su nombre y precio.
a) Estructuras de datos. Utilizar en cada subapartado las estructuras de los anteriores.
a.1) Definir la estructura más adecuada para guardar un carro de la compra.
a.2) Definir la estructura más adecuada para guardar una caja registradora.
a.3) Definir la estructura más adecuada para guardar el supermercado.
b) Implementar un algoritmo denominado atender_cliente que dado un carro de la compra,
pase los productos que contiene por caja y calcule el precio total.
c) Implementar un algoritmo que calcule la recaudación de las cajas después de pasar los
primeros x carros por cada una de ellas. x será un argumento de entrada que puede ser mayor que
el número de carros en espera en algunas de las cajas.
Utilizar la estructura de datos más adecuada para devolver el resultado pedido.
94
Este algoritmo intenta contar la longitud de la rama más larga del árbol.
a) ¿Qué tipo de recorrido hace el algoritmo MISTERIO?
b) ¿Qué error tiene el algoritmo? ¿Cómo lo arreglarías?
4. Dado el algoritmo,
PROCEDURE ¿Que_hace_esto? (v: VECTOR, n: INTEGER; VAR A: TArbol);
VAR
p, padre, aux: TArbol;
i : INTEGER;
BEGIN
CrearArbol(A);
New(p);
p^.info:= v[1];
p^.izq:= NIL;
p^.der:= NIL;
A:= p;
FOR i:= 2 TO n DO
BEGIN
New(p);
p^.info:= v[i];
p^.izq:= NIL;
p^.der:= NIL;
aux:= A;
95
IF v[i] < DatoRaiz(padre) THEN
padre^.izq:= p
ELSE
padre^.der:= p
END;
Imprimir(A)
END;
Se pide:
a) ¿Qué hace este algoritmo?
b) ¿Qué recorrido elegiríamos en el algoritmo Imprimir, si se desea que los valores de v se
impriman ordenados y por qué?
5. Dado el algoritmo,
6. Dado el algoritmo,
96
Ejercicios Olimpiadas
1.- ANAGRAMAS
Dado un conjunto de letras, escribe un programa que genere todas las palabras posibles.
Ejemplo: De la palabra 'abc' el programa debe producir, explorando todas las posibles combi-
naciones de letras, las siguientes palabras: 'abc', 'acb', 'bac', 'bca', 'cab' y 'cba'.
En la palabra obtenida del archivo de entrada, algunas letras pueden aparecer más de una
vez. Para una palabra dada, el programa no debe producir la misma palabra más de una vez y
las palabras deben aparecer ordenadas alfabéticamente.
Los datos de entrada están en el archivo de texto ANAGRA.IN que está formado por diversas pa-
labras. La primera línea contiene el número de palabras que siguen. Cada línea contiene una
palabra. Una palabra está compuesta por letras de 'a' a 'z', del alfabeto sajón, mayúsculas y
minúsculas. Las letras mayúsculas y minúsculas deben considerarse diferentes.
Los datos de salida estarán en el fichero de texto ANAGRA.OUT, para cada palabra del archivo de
entrada, el archivo de salida debe contener todas las diferentes palabras que pueden ser gene-
radas con las letras de la palabra. Las palabras generadas desde una palabra deben mostrarse
en orden alfabético.
Ejemplo:
ANAGRAMA.IN ANAGRA.OUT
2 abc
Abc acb
acba bac
bca
cab
97
cba
aabc
aacb
abac
abca
acab
acba
baac
baca
bcaa
caab
caba
cbaa
Consideremos una población de K insectos en una tabla (M x N), de modo que en cada celda
de la tabla hay, como máximo, un insecto. Por lo tanto, cada insecto tiene, como máximo, 8 ve-
cinos. La población se está desarrollando continuamente debido a los nacimientos y defunciones
que se producen. Las reglas de evolución que se observan son las siguientes:
Escribe un programa que simule la evolución de la población y que determine cómo estará la po-
blación después de L ciclos evolutivos.
Los datos de entrada están en el archivo de texto VIDA.IN, en la primera línea hay 3 enteros
separados por un espacio en blanco N, M y L que representan al número de filas, de colum-
nas y de ciclos, respectivamente. Las siguientes N líneas contienen la ubicación de los insectos
representados por un asterisco '*' y por un punto '.' las celdas vacías.
Los datos de salida estarán en el fichero de texto VIDA.OUT, en el que habrá N líneas con la
ubicación de los insectos tras L ciclos. En cada línea un '.' representa a una celda vacía y un
'*' a un insecto.
Ejemplo:
VIDA.IN VIDA.OUT
6 6 1 ......
...... ..***.
...**. ..*...
..**.. ..**..
...*.. ......
...... ......
......
En una biblioteca infantil se ha diseñado un sistema para determinar cada día en qué orden
elegirán los niños el libro que desean leer. Para ello los bibliotecarios han decidido seguir la si-
guiente estrategia: cada día se formará un círculo con todos los chicos. Uno de los chicos es
elegido al azar como el número 1 y el resto son numerados en orden creciente hasta N si-
guiendo el sentido de las agujas del reloj.
Comenzando desde 1 y moviéndose en sentido horario uno de los bibliotecarios cuenta k chicos
mientras el otro bibliotecario comienza en N y se mueve en sentido antihorario contando
m chicos. Los dos chicos en los que se paren los bibliotecarios son elegidos; si los dos biblioteca-
rios coinciden en el mismo niño, ese será el único elegido.
Cada uno de los bibliotecarios comienza a contar de nuevo en el siguiente chicos que permanezca
en el círculo y el proceso continúa hasta que no queda nadie.
Tener en cuenta que los dos chicos abandonan simultáneamente el círculo, luego es posible que
uno de los bibliotecarios cuente un chico ya seleccionado por el otro.
Se pide la construcción de un programa que, dado el número N de chicos y los números k y m que
utilizará cada bibliotecario en la selección, indique en qué orden irán siendo seleccionados los chi-
98
Formato de la salida (a guardar en el fichero de caracteres " BIBLIO.OUT"): Para cada línea
de datos del fichero de entrada (excepto, claro está, la última que contiene tres ceros), se
generará una línea de números que especifique el orden en que serían seleccionados los chicos
para esos valores de N, k y m. Cada número de la línea ocupará tres caracteres en el fichero (no
llevarán ceros a la izquierda y serán completados con blancos por ese lado hasta alcanzar ese
tamaño). Cuando dos niños son seleccionados simultáneamente, en el fichero aparecerá primero el
elegido por el bibliotecario que cuenta en sentido horario. Los grupos de elegidos (de uno o dos
niños cada uno) vendrán separados entre sí por comas (no debe ponerse una coma después
del último grupo). La salida correspondiente a la entrada dada como ejemplo tendría que ser
exactamente la siguiente; para ver con claridad cuántos blancos deben aparecer, representamos
cada carácter blanco con un carácter '*' (en el fichero generado, pues, no aparecen asteriscos sino
espacios en blanco).
Ejemplo:
BIBLIO.IN BIBLIO.OUT
10 4 3 **4**8,**9**5,**3**1,**2**6,*10,**7
5 2 8 **2**3,**5,**4**1
13 2 2 **2*12,**4*10,**6**8,**9**5,*13**1,**7,**3*11
0 0 0
4.- CAMINOS
Sea un tablero de dimensiones MxN, 1<=M<=9, 1<=N<=9, tal que cada casilla contenga una
letra mayúscula. La casilla que está en la fila m y la columna n la identificamos mediante (m,n).
Dos casillas diferentes (mi,ni) y (mj,nj) son adyacentes si se cumple:
Es decir, son adyacentes todas aquellas casillas que rodean a una dada, considerando que en el
tablero como si la última fila estuviera unida a la primera, y lo mismo para las columnas. En
el dibujo siguiente marcamos con un asterisco las casillas adyacentes a las casillas (2,3)(a la
izquierda) y (1,1) (a la derecha) en un tablero
4x4:
.*** .*.*
.*.* **.*
.*** ....
.... **.*
Dada una palabra de k letras mayúsculas A=a1 a2 ... ak, k>=1, decimos que A está conteni-
da en el tablero si se cumple que:
SHAZ
IOLG
EZEF
OHDI
En el caso de "SOLA", las casillas que forman su camino son (1,1), (2,2), (2,3) y (1,3). Para
"HOLA", son (1,2), (2,2), (2,3) y (1,3). Para "ADIOS", el camino es (1,3), (4,3), (4,4), (4,1) y
(1,1).
99
Formato de la entrada (residente en el fichero de caracteres "CAMI.DAT"):
• Línea 1: valores de M y N (un carácter del '1' al '9') separados por un único blanco
• Líneas de la 2 a la M+1 (la línea k representa la fila k-1 del tablero): N caracteres,
representando el contenido de la línea correspondiente del tablero
• Línea M+2: p caracteres, M*N>=p>=1, que representa la palabra a tratar.
Ejmplo:
CAMINO.IN CAMINO.OUT
44 11
SHAZ 22
IOLG 23
EZEF 13
OHDI
SOLA
5.- VA DE UNOS
Dado cualquier número entero N del rango [1..10000] no divisible por 2 o 5, algún múltiplo de N es
un número que en notación decimal es una secuencia de unos. Hay que encontrar el número de
dígitos que tiene el menor múltiplo de N que está formado solo por unos.
Por ejemplo, si N es 3, el menor múltiplo de 3 que está formado solo por unos es 111. Por lo tanto
la solución sería 3.
Los datos de entrada están en el fichero UNOS.IN, en el que hay una sola línea que contiene un
número entero positivo del rango antes mencionado.
Los datos de salida estarán en el fichero UNOS.OUT que contendrá una sola línea que contendrá el
número de unos que tiene el menor múltiplo de N que está formado solo por unos.
Ejemplo:
UNOS.IN UNOS.OUT
7 6
Dada una expresión aritmética tal como la (a+b)*(c+d) se puede representar con la notación po-
laca inversa de forma que cada operador va después de sus dos operandos de la forma:
ab+cd+*
Datos de entrada:
El programa debe leer los datos de entrada del archivo NOPOIN.IN. En la primera línea figura la
expresión a evaluar. Ésta está formada por enteros del rango [–32000..32000] y por los operadores
+, -, * y /. Un espacio en blanco separa a cada operando y a cada operador. La expresión es
sintácticamente correcta.
Datos de salida:
El archivo de salida NOPOIN.OUT consistirá de una sola línea donde aparece el entero que es el
valor de la expresión evaluada.
EJEMPLO:
NOPOIN.IN NOPOIN.OUT
10 8 + 4 2 - * 36
En el año 1179 el Papa Alejandro III, a través de la bula “Regis Aeterni”, confirma la gracia
del privilegio jubilar, concedida a Compostela por el Papa Calixto II a petición del arzobispo Diego
100
Debe tenerse en cuenta que el actual calendario “gregoriano” fue establecido por el Papa
Gregorio XIII en el año 1582 (mediante la bula “Inter gravissimus”) para mejorar la precisión del
calendario “juliano” vigente en aquel momento. Para ello se corrigió la “regla de los bisiestos” (que
indicaba que todos los años múltiplos de 4 tenían un día extra después del 28 de febrero), estable-
ciendo que los múltiplos de 100 que no lo fuesen de 400 no serían, a partir de ese momento, bi-
siestos. Paracorregir la desviación que había provocado hasta entonces la imprecisión del calendario
juliano, ese año se suprimieron los 10 días que van del 5 al 14 de octubre, de modo que al jueves 4
de octubre le siguió el viernes 15.
Objetivo
Se trata de calcular cuantos años santos hay entre dos años dados (ambos incluidos).
Entrada
En un archivo de texto con nombre "SANT.IN" se pasarán al programa una lista de pares de años
(un par por línea), todos ellos posteriores al año 1179 y anteriores al año 100000. Los dos años de
cada par aparecerán separados unicamente por espacios en blanco (uno o más). No habrá otros
caracteres ni al principio ni al final de las líneas.
Salida
El programa deberá escribir, en un archivo de texto con nombre "SANT.OUT", una línea por cada
línea del archivo de entrada, indicando para cada intervalo, el número de años santos que le co-
rresponde.
Ejemplo
SANT.IN SANT.OUT
1998 2000 1
1998 2004 2
1999 2005 2
El rectángulo de Kubrik es una adaptación a dos dimensiones del clásico cubo de Rubik. Se dispone
de un rectángulo de 8 casillas, distribuidas en dos filas y cuatro columnas, coloreadas con 8 colores
diferentes, que aquí representamos con un número del 1 al 8. Inicialmente, la disposición de los
colores en las casillas es la siguiente:
1234
8765
Se pide, a partir de esta disposición inicial, alcanzar una disposición objetivo caracterizada por una
configuración diferente del rectángulo. Un ejemplo podría ser la configuración final:
5183
4762
Para ello deberán aplicarse sobre la disposición inicial una serie de transformaciones hasta alcanzar
la disposición objetivo. Existen tres tipos de transformaciones, identificadas por las letras A, B y C.
8765
1234
1234
8765
4123
5876
1234
8765
101
1724
8635
Concretamente, el programa pide una secuencia mínima de transformaciones que lleve de la confi-
guración inicial a la configuración destino. En caso de haber más de una secuencia mínima, se pue-
de devolver cualquiera de ellas.
Formato de la entrada (residente en el fichero de caracteres "KUBRIK.IN"): una línea con ocho
caracteres entre '1' y '8', sin repetición y separados exactamente por un blanco (no hay ningún otro
tipo de caracteres ni al inicio ni al final de la línea), que representan la configuración final, numera-
das a partir del vértice superior izquierdo en el sentido del movimiento de las agujas del reloj.
Formato de la salida (a guardar en el fichero de caracteres "KUBRIK.OUT"): una línea inicial dicien-
do la longitud de la secuencia mínima y, a continuación, tantas líneas como transformaciones apli-
cadas, en el orden de aplicación. No debe aparecer ningún otro tipo de información en la línea.
Podéis trabajar con la seguridad de que existe una secuencia de movimientos que llevan de la con-
figuración inicial a la configuración final.
Ejemplo:
KUBRIK.IN KUBRIK.OUT
51832674 4
C
A
B
C
102