Sie sind auf Seite 1von 102

Av. Juán B.

Justo Nº 4287
(0223) 472-2408
Mar del Plata

ABDALA - MARMOL
Unidad Nº1 Algoritmos

Contenidos:

 Conceptos Iniciales. (Definición de Algoritmo, Computadora y Programa) 4


 Metodología de la Programación 5

Unidad Nº 2 Estructura de los Programas

Contenidos

 Comentarios 6
 Sentencia Program 6
 Declaración de Unidades 7
 Programa Principal 7
 Compilación y Ejecución en memoria 7

Unidad Nº 3 Entornos y Programa Pascal

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

Unidad Nº 4 Estructuras de Control

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

Unidad Nº 5 Procedimientos y Funciones

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

Algoritmos y estructuras de datos


o Aritméticas. 22
o Trigonométricas. 22
o Generación de números aleatorios. 22
 Comparación de cadenas. 23
o Length 23
o Operador + 23
o Concat 23
o Pos 23
o Copy 23
o Insert 23
o Delete 23
o Upcase 23
o Str 23
o Val 23

 Trabajo Práctico Procedimientos y funciones 87

Unidad Nº 6 Arreglos o vectores

Contenidos.

 Datos enumerados y subrango 27


 Introducción a datos estructurados 27
 Arreglos
o Unidimensionales 28
o Bi dimensionales (matrices) 28
o Matrices tridimensionales 29
 Vectores Paralelos. 30
 Algoritmos de búsqueda en arrays 31
o Búsqueda secuencial 89
 Trabajo Práctico vectores

Unidad Nº 7 Introducción a la recursión

Contenidos.

 Recursión, concepto 32
 La sucesión de Fibonacci 34
 Torres de Hanoi 35
 Trabajo Práctico Recursividad. 90

Unidad Nº8 Registros

 Registros con variantes 37


 Manejo de registros: acceso a componentes y operaciones. 38
 Registros con variantes. 39
 Trabajo Práctico Registros 91
Unidad Nº 9 Punteros

 Introducción al uso de punteros 41


 Definición y declaración de punteros 42
 El operador de dirección @ 42
 Generación y destrucción de variables dinámicas 42
 Operaciones básicas con datos apuntados 43
 Operaciones básicas con punteros
 El valor nil 43
 Aplicaciones no recursivas de los punteros 45
 Asignación de objetos no simples
 Funciones de resultado no simple
 Trabajo Práctico Punteros 92

Unidad Nº 10 Estructuras de datos recursivas – Listas

 Definición del tipo lista 47


 Inserción de elementos 48
 Eliminación de elementos 50
 Otras operaciones sobre listas 51
 El procedimiento Eliminar K-esimo 52
 El procedimiento Insertar K-esimo 53
 Implementación dinámica de una lista con enlace simple 53
 Implementación dinámica como lista doblemente enlazada. 57

3
 Multilista 59

Unidad Nº 11 El tipo Pila , Cola


59
60
 El tipo Pila Definición y ejemplos 62
 Implementación dinámica del tipo Pila 93
 Estructura de datos Cola 94
 Trabajo Práctico Pilas
 Trabajo Práctico Colas

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

Algoritmos y estructuras de datos


Unidad Nº 1 Algoritmos

Conceptos iniciales:

La algoritmia es uno de los pilares de la programación y su relevancia se muestra en el


desarrollo de cualquier aplicación, más allá de la mera construcción de programas.
Este es un apunte introductorio al análisis y diseño de algoritmos que pretende exponer a
los alumnos las técnicas básicas para su diseño e implementación.
Se presentarán las técnicas básicas de resolución de problemas en base a unos ejemplos
clásicos, para después dejar propuesta al alumno una colección de problemas sobre cada tema,
intentando en cada ejercicio proporcionarle un nuevo enfoque para abordar los problemas o permi-
tirle combinar algunas de las técnicas, lo que enriquece el estudio de los métodos y algoritmos
tratados. Enriqueciéndolos para abordar los problemas de una forma unificada y coherente.

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.

Programa: Es el algoritmo escrito en algún lenguaje de programación de computadoras.

El concepto de algoritmo es fundamental en el proceso de programación de una computa-


dora, pero si tomamos el trabajo de observar detenidamente a nuestro alrededor, descubriremos
que hay algoritmos en todas partes: Nos están dando un algoritmo cuando nos indican la forma de
llegar a una dirección dada, o cuando nos dan una receta de cocina. También encontramos algo-
ritmos en los manuales de instrucciones de cualquier aparato electrónico. Seguimos algoritmos
cuando nos conducimos en un automóvil o cualquier tipo de vehículo. Todos los procesos de cálculo
matemático que normalmente realiza una persona en sus tareas cotidianas, como sumar, restar,
multiplicar o dividir.

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.

Ventana del Programa Turbo Pascal

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.

PARA WINDOWS 32-BIT

Algoritmos y estructuras de datos


DOS Lleva rutinas de emulación de acceso al sistema DOS mediante funciones del sistema de Win-
dows.
CRT Rutinas b·sicas de gestión de la pantalla en modo texto.
GRAPH Gestión básica de gráficos.
WINDOWS Esta unit permite acceder a las funciones de la API de 32-BIT de Windows.
OPENGL Accede a les funciones de bajo nivel OpenGL de Windows 32-BIT.
WINMOUSE Funciones para emplear el ratÛn en Windows 32-BIT.
OLE2 Implementa las posibilidades OLE de Windows 32-BIT.
WINSOCK Interfaz a la API winsock de Windows 32-BIT.
SOCKETS Encapsulación de WINSOCK de forma que sea compatible en Windows 32-BIT y en Li-
nux..
Cada unidad que se declara deberá estar separada de la siguiente por una coma. Al final
de todas las unidades declaradas se deberá colocar un punto y coma “;”.

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.

Ya conociendo la estructura es posible escribir otro ejemplo de programa:

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 y ejecución en memoria

La compilación de un programa es el paso mediante el cual traducimos dicho programa al


lenguaje maquina entendible por la computadora.

Para lograr la compilación en el entorno integrado de desarrollo de Turbo Pascal se utiliza


la opción Compile del menú del mismo nombre. Para acceder al menú se utiliza la secuencia de
teclas: [ALT] + [C], y luego se escoge la opción Compile.

Otra forma de realizar la compilación es con la secuencia de teclas: [ALT] + [F9].

Es posible compilarlo y ejecutarlo automáticamente utilizando la secuencia: [CONTROL] +


[F9]

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

Una vez compilado un programa en el disco es posible ejecutarlo directamente desde el


sistema operativo.

Entornos y Programa Pascal

Tipos de datos

Estructuras de datos

Es el modelo matemático o lógico de una organización particular de datos. Los tipos de


datos mas frecuentes utilizados en los diferentes lenguajes de programación son:

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

El manejo de la información en Turbo Pascal se realiza mediante diferentes clases de datos.


En este apartado se tratarán los principales tipos y conforme se vayan necesitando se explicaran los
demás.

Variables y constantes

Los tipos de datos que manejaremos en nuestro programa pueden ser de dos clases: varia-
bles o constantes.

Como su nombre lo indica las variables pueden cambiar a lo largo de la ejecución de un


programa, en cambio las constantes serán valores fijos durante todo el proceso.

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.

Ahora, si nuestro programa de operaciones matemáticas va a utilizar el valor de PI para


algunos cálculos podemos definir un identificador PI con el valor de 3.1415926 constante, de tal
forma que PI no pueda cambiar de valor, ahora en lugar de escribir todo el número cada vez que
se necesite en nuestro programa, solo tenemos que escribir PI.

Las variables y constantes pueden ser de todos los tipos vistos anteriormente: numéricos
tanto enteros como reales, caracteres, cadenas de caracteres, etc.

Declaración de constantes y variables

Algoritmos y estructuras de datos


Para declarar las constantes se utiliza la palabra reservada CONST seguida de un identifica-
dor al que se le dará un valor determinado, un signo de igual “=”, el valor que recibirá el identifica-
dor y al final un punto y coma “;”. Ejemplo:

CONST pi = 3.1415926;

De esta forma el identificador pi recibirá el valor de 3.1415926 y no será posible cambiarlo


en el transcurso del programa.

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;

Otra forma de escribir lo mismo es así:

CONST PI = 3.1415926; Nombre = ‘Juan Gutiérrez’; Unidad = 1;

Pero por cuestiones de legibilidad es preferible la primera opción.

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:

VAR Num_entero : Integer;


Nombre : String;

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:

Tipo Rango de valores que acepta


Integer -32,768 a 32,767
Word 0 a 65535
ShortInt -128 a 127
Byte 0 a 255
LongInt -2,147,483,648 a 2,147,483,648

Al utilizar los tipos enteros es posible representar en el programa un número en formato


hexadecimal, para hacer esto solo se le antepone el símbolo “$” al valor hexadecimal, al momen-
to de visualizar dicho valor, o utilizarlo en alguna operación será como decimal. Por ejemplo:

Cantidad := $10;

El valor que se guarda en “Cantidad” es 16.

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:

3.0E5 = 3.0 * 10^5 = 3.0 * 100000 = 300000


1.5E-4 = 1.5 * 10^-4 = 1.5 * 0.0001 = 0.00015

Tipos Char (caracter)

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

Donde Variable es la variable a definir y Tamaño es el número máximo de caracteres que


podrá contener esa variable (naturalmente mayor a 0 y menor a 256).

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

Algoritmos y estructuras de datos


PROGRAM Cadena_de_caracteres;

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.

Asociadas al tipo boolean, se tienen las siguientes operaciones:

not negación lógica (con la precedencia más alta)


and conjunción lógica (precedencia multiplicativa)
or disyunción lógica (predecencia aditiva)

y su funcionamiento viene dado por la siguiente tabla de verdad:

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

Son las operaciones binarias de comparación siguientes:

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

Los operadores de Turbo Pascal son:

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:

Nombre := ‘Juan Pérez’;


{Nombre guardará la cadena “Juan Pérez”}
Resta := Numero1 – Numero2;
{Resta guardará el resultado de la resta de Numero2 a Numero1}
Área := (Base*Altura)/2;
{Obtiene el área de un triangulo y la guarda en el identificador Área}

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.

Para identificar una sentencia compuesta de un grupo sucesivo de sentencias se encierran


entre las palabras reservadas BEGIN y END. Uno de los ejemplos más claros de una sentencia
compuesta es el cuerpo de un programa principal en Turbo Pascal, el lenguaje toma todo lo que
existe entre estas dos sentencias como un solo elemento a ejecutarse aún cuando contenga varias
instrucciones o sentencias:

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:

FOR identificador := inicio TO fin DO instrucción;

FOR VC:= VI TO VF DO Pseudocódigo

Sentencia; Para VC:= VI hasta VF hacer

Donde: Sentencias

VC: es una variable de control Sentencias


VI: valor inicial
VF: Valor final y VI<=VF Fin Para

12

Algoritmos y estructuras de datos


Donde el identificador es la variable que se incrementará, VI es el primer valor que tendrá
dicha variable y VF es el valor hasta el cual se incrementará la misma; instrucción es la sentencia
(sencilla o compuesta) que se hará en cada incremento de la variable.

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.

El siguiente ejemplo escribe los números del 1 al 50 en pantalla. La variable utilizada es


“Numero”.

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:

WHILE condición DO instrucción

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.

WHILE verifica el valor de verdad de una expresión lógica. Si el resultado es verdadero,


ejecuta la /s sentencia /s, y vuelve a verificar la expresión. Mientras sea verdadera, sigue ejecutan-
do la /s sentencia /s. Cuando es falsa, termina el ciclo, y el control de ejecución sigue en la senten-
cia siguiente a WHILE.

Pseudocódigo

While Expresión Lógica DO Mientras Expresión Lógica Hacer

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

PROGRAM Ciclo_WHILE; Inicio


VAR
Numero : Integer;

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

Until Expresión Lógica Sentencias

Hasta que Expresión Lógica

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

Bucles controlados por condición:

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.

1. Solicitar al usuario la continuación del bucle.

Este método consiste simplemente en pedir al usuario si existen mas entradas.

Suma:= 0;
Writeln(‘¿ Existen mas números en la lista?) (S/N)’);
Readln (respuesta);
While (respuesta = ‘S’ ) or (respuesta =’s’)

14

Algoritmos y estructuras de datos


Begin
Writeln(‘introduzca un numero:’);
Readln(numero);
Suma:= suma + numero; {acumula en suma los valore de suma}
Writeln(‘ ¿Existen mas números (s/n)?’);
Readln(respuesta);
End;

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.

3. Uso de banderas o interruptores

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.

Se leen números enteros hasta que se introduce un valor menor a ce-


ro. La variable lógica Positivo actúa como indicador o bandera del
programa para señalar si un suceso de entrada de un número positivo
ha ocurrido.

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

A la hora de describir algoritmos, puede ser necesario especificar dos o más


caminos alternativos a seguir en función de cierta condición. La estructura de selección permite
ejecutar una acción u otra, dependiendo de una determinada condición que se analiza a la entrada
de la estructura.
Esta expresión es utilizada para ejecutar una sentencia en el caso que una condición esta-
blecida sea verdadera, de lo contrario se podrá ejecutar una sentencia distinta. Su sintaxis es:

IF condición THEN instrucción ELSE otro

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:

IF Numero > 5 THEN


BEGIN
IF Numero < 10 THEN Opcion := Numero;
IF Numero < 30 THEN Opcion2 := Numero;
END;

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.

El siguiente programa ilustra el uso de la forma CASE, el programa preguntará un número


al usuario y lo clasificará de acuerdo a su valor.

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

Algoritmos y estructuras de datos


ELSE WriteLn(‘El número no estaba en el rango indicado’);
End;
readln;
END.

Procedimientos y Funciones

Definición de Procedimiento

Un procedimiento es un grupo de sentencias que realizan una tarea concreta. En lugar de


reescribir el código completo de esa tarea cada vez que se necesite, únicamente se hace una refe-
rencia al 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.

Creación de los procedimientos

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;

Uso de los procedimientos

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.

El siguiente programa ilustra el uso o la llamada al procedimiento creado anteriormente:

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;

Variables locales y globales

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:

PROCEDURE nombre (lista de parámetros);

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:

Procedure Ejemplo(a, b : Integer; c, d : String);

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:

Ejemplo(1, 2, ‘Hola’, ‘Adiós’);

Ejemplo de un programa con procedimiento que utiliza un parámetro.

PROGRAM Ejemplo;
Uses crt;
VAR

18

Algoritmos y estructuras de datos


Saludo : String;

PROCEDURE Imprime_5 (a : String);


VAR
Contador : Integer;
BEGIN
FOR contador := 1 to 5 DO
{Imprime 5 veces la cadena almacenada }
WriteLn(a);
{en la variable “a”, que es la información}
END;
{que llega como parámetro }

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

Parámetros formales y reales

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.

En cambio, a los argumentos concretos utilizados en la llamada de un subprograma se les llama


parámetros reales. (En ingles, actual parameters, lo que ha dado lugar en ocasiones a la traduc-
ción errónea “parámetros actuales" en castellano.)

Mecanismos de paso de parámetros

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:

a:= 10; {a = 10}


Write(a) {aparece el valor de a en la pantalla}
{a = 10}

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.

Esquemáticamente tenemos que:

a:= 10; {a = 10}


Read(a) {el usuario da el valor 20 por el teclado}
{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:

• parámetros por valor:

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.

• parámetros por referencia (o por dirección o por variable):

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.

En el paso de parámetros por valor,

procedure EscribirSiguiente (v: integer);


{Efecto: escribe en la pantalla v + 1}
begin
v:= v + 1;
WriteLn(v)
end; {EscribirSiguiente}

la siguiente secuencia de instrucciones produce la salida que se muestra a la derecha:

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.

En el paso de parámetros por referencia,

procedure IncrementarYescribir (var v: integer);


Begin
v:= v + 1;
WriteLn(v)
end; {IncrementarYescribir}

la siguiente llamada produce esta salida:

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

Algoritmos y estructuras de datos


Funcionamiento de una llamada

Veamos como se realiza la llamada a un subprograma y como se produce el paso de parámetros


utilizando un ejemplo con un procedimiento para la lectura de números enteros y con la funcion
Fac dentro de un programa completo:

procedure LeerNumPos(var n: integer);


{Efecto: solicita un entero hasta obtener uno positivo}

Begin
{2A}
Repeat
Write('Escriba un entero positivo: ');
ReadLn(n)
until n >= 0
{2B}
end; {LeerNumPos}

function Fac(num: integer): integer;


{Dev. num!}

var
i, prodAcum: integer;
begin
{4A}
prodAcum:= 1;
for i:= 2 to num do
prodAcum:= prodAcum * i;
Fac:= prodAcum
{4B}
end; {Fac}

Begin {Programa principal}


{1}
LeerNumPos(numero); {num >= 0}

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

Al terminar la función, su valor se devuelve al programa principal, termina la escritura y finalmente


termina el programa (estado {5}).

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.

Definición de las funciones

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:

FUNCTION nombre(parámetros) : tipo_de_datos;

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:

FUNCTION Promedio (a, b : Real) : Real; {Promedio de dos números reales}


BEGIN
Promedio := (a + b) / 2;
END;

Uso de las funciones

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;

FUNCTION Promedio (a, b : Real) : Real;


{Promedio de dos números reales}
BEGIN
Promedio := (a + b) / 2;
END;

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

Algoritmos y estructuras de datos


End.
Readln Lee un dato ingresado por teclado
var
s : String;
begin
Write('Introduzca una línea de texto: ');
Readln(s);
Writeln('Ha tecleado: ',s);
Writeln('Pulse <Enter> para salir');
Readln;
End.
DIV Da el resultado entero de una división entre enteros.
Mod Regresa el residuo (resto) de una división de enteros.
PROGRAM Operaciones_Básicas;
VAR
Suma, Resta, multiplicacion, Division : Real;
Cociente_Entero, Resto_Entero : Integer;

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.

Ejemplo de un programa generador de números aleatorios:

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

Algoritmos y estructuras de datos


R := Int(-123.456); { -123.0 }
End.
Pi Devuelve el valor de Pi(3.1415926535897932385)
begin
Writeln('Pi = ',Pi);
End.
Odd Devuelve un resultado lógico si el argumento es impar.
begin
if Odd(5) then
Writeln('5 es impar')
else
Writeln('Algo NO es impar');
end.
Sin Devuelve el Seno del argumento
var
R: Real;
begin
R := sin(90); writeln(R:2:2); readln;
end.

Comparación de cadenas UTILIZACIÓN DE STRINGS

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.

Manejo de los elementos de la cadena

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:

Cadena1 := 'Buenos ';


Cadena2 := 'días ';
Cadena3 := Cadena1 + Cadena2;
WriteLn (Cadena3);
Concat Es posible incluir cualquier número de cadenas que se necesiten concatenar.
Var
Cad1,cad2,cad: string[40];
Begin
Cad1:= ‘programa’;
Cad2:= ‘de computadora’;
Cad:= concat(cad1,cad2); readln;
End.
Pos Sirve para localizar una determinada cadena dentro de otra, en otras palabras para veri-
ficar si una cadena es subcadena de otra segunda
Cadena := 'Domingo Lunes Martes Miércoles Jueves Viernes Sábado';
WriteLn(Cadena);
WriteLn(Pos('Lunes', Cadena)); {Muestra 9}
WriteLn(Pos('Jueves', Cadena)); {Muestra 32}
WriteLn(Pos('Ayer', Cadena)); {Muestra 0}
Copy Regresa una subcadena de una variable o constante dada como parámetro.
Cadena := "Nuevos horizontes";
WriteLn (Copy(Cadena, 8, 10)); {Visualiza: horizontes}
Insert Sirve para insertar una cadena dentro de otra en alguna posición determinada.

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)

El resultado de la ejecución es 23.7, por el contrario si se cambia Ch por

Ch:=’23x.7’;

26

Algoritmos y estructuras de datos


La ejecución producirá la visualización de 3 (posición del carácter X)

Tipos de datos estructurados

Los datos estructurados

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

El tipo enumerado define un conjunto de valores definidos y ordenados por el programador


(ej. Definidos por enumeración). Estos valores serán formados por las variables de ese tipo.

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;

Las variables M1 y M2 pueden tomar


como contenido los valores informáti-
ca, construcciones o electrónica.

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

sabado, domingo son valores ambiguos.

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

Identificador de tipo = constante1.. constante2;

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;

Introducción a Datos Estructurados

Arreglos, Vectores o Arrays

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.

Propiedades de los arreglos:

1.- Todas las componentes de un arreglo poseen el mismo tipo de dato.

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

Algoritmos y estructuras de datos


límite puede ser positiva, negativa o cero. Los limites de una dimensión se definen en un a especifi-
cación de arreglo.

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.

5.- Cada componente de un arreglo se denota explícitamente y es accedida, directamente, mencio-


nando el nombre del arreglo seguido de una expresión encerrada entre corchetes, a la que llama-
remos índice del arreglo (o simplemente subíndice).

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:

Veamos ahora un ejemplo de manejo de vectores en Pascal.


Para indicar cuantos viajeros van en cada uno de los 15 vagones de un tren (con una capacidad
máxima de 40 personas por vagón), en lugar de utilizar 15 variables enteras (una para cada
vagón), se puede y se debe utilizar un vector de la siguiente forma:

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

que en Pascal se calcula como si- En Pseudocódigo


gue:
Total:=0
total:= 0; Para i:= 1 hasta 15 hacer
for i:= 1 to 15 do Total:= total + vagón[i]
total:= total + vagon[i] Fin para

Matrices

Los arrays multidimensionales reciben el nombre genérico de 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;

procedure LeerMatriz(var mat : tMatriz);


{Efecto: Este procedimiento lee del input una matriz cuadrada mat 2MN( IR), com-
ponente a componente}

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}

procedure MultiplicarMat(m1, m2: tMatriz; var resul: tMatriz);


{Efecto: resul:= m1 * m2}
var
i, j, k: 1..N;
{ recorre las filas de m1 y de resul, j recorre las columnas de m1 y las filas de
m2 y k recorre las columnas de m2 y las de resul}

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}

procedure EscribirMatProd(m: tMatriz);


{Efecto: escribe en la pantalla los elementos de la matriz m}
var
i, j: 1..N;

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

Imaginemos que una constructora acaba de finalizar un grupo de 12 Edificios (numerados


del 1 al 12), cada uno de los cuales tiene 7 pisos (numeradas del 1 al 7) y en cada planta hay 3
viviendas (A, B y C). Supongamos que el encargado de ventas quiere llevar un control lo mas senci-
llo posible sobre que viviendas se han vendido y cuales no. Para ello, podríamos utilizar 12* 7* 3 =
252 variables de tipo boolean de la forma: bloqueiPlantajLetraX asignándole un valor True para
indicar que la vivienda del bloque i, planta j, letra X esta vendida o bien False para indicar que no lo
esta.

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

Algoritmos y estructuras de datos


indicaría el número de Edificio, la segunda dimensión el piso, y la tercera la letra de la vivienda.
Axial, para indicar que en el edificio 3, el piso 5ºA esta vendido, asignaremos un valor True el ele-
mento que ocupa la posición [3,5,'A'] de este dato estructurado; mientras que si en el edificio 5, el
1ºC sigue estando disponible, asignaremos un valor False a la posición [5,1,'C'].

Vectores Paralelos

Consiste en almacenar y recuperar los ítems de información asociada del mismo índice en
cada uno de los vectores paralelos.

Ejemplo: Datos sobre departamentos de un edificio

Dpto. Apellido y Nombre Identificador Expensas ¿Pago?


1 Ravera Mariana 3B 150$ Si
2 Achaval Susana 9A 170$ Si
3 Medina Juan 2C 152$ No
4 Quiroga Roberto 5J 60$ No

AyN Dpto Expen Pago


(Vector de String) (vector de String) (vector de reales) (vector Booleano)

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

Dpto, ayn: vec1;


Expen:vec2;
Pago:vec3;
N,I,J,K: integer;
Aux: char;

Begin

Write (‘ Ingrese cantidad de departamentos’ );


Readln (N);
For I:=1 to N do
Begin
Writeln (‘ Departamento ‘, i);
Write(‘ Ingrese Apellido y Nombre’); Readln(ayn[i]);
Write(‘ Ingrese Num Dpto.’); Readln(dpto[i]);
Write(‘ Ingrese Expensas’); Readln(Expen[i]);
Write(‘ Ingrese “S” si pago, “N” sino pago”’);
Readln(aux);
If aux = ‘S’ then
Pago[i]:=true
Else
If aux= ‘N’ then
Pago[i]:=false
Else
Write(‘ codigo erroneo.’);
End;

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.

Algoritmos de búsqueda en arrays

Es evidente que, si tenemos datos almacenados, es interesante disponer de algún meca-


nismo que permita saber si un cierto dato esta entre ellos, y, en caso afirmativo, localizar la posi-

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

La búsqueda secuencial consiste en comparar secuencialmente el elemento deseado con los


valores contenidos en las posiciones 1, . . . , n hasta que, o bien encontremos el índice i buscado, o
lleguemos al final del vector sin encontrarlo, concluyendo que el elemento buscado no esta en el
vector.

function BusquedaSec(v: tVector; elem: tElem): tIntervalo;

var
i: tIntervalo;

begin
i:= 0; {se inicia el contador}

repeat
i:= i + 1
until (v[i] = elem) or (i = N);

if v[i] = elem then {se ha encontrado el elemento elem}

BusquedaSec:= i
else
BusquedaSec:= 0
end; {BusquedaSec}

Unidad Nº 7 Introducción a la recursión

Un subprograma (procedimiento o función) recursivo es aquel que se llama a sí mismo. En


Pascal los procedimientos y las funciones pueden ser definidos como modo recursivo. La recursivi-
dad es una alternativa a la repetición, existen muchas situaciones en las que la recursividad es una
solución simple y natural a un problema en que caso contrario sería difícil del resolver.

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.

Consideremos el cálculo del factorial de un entero positivo n que se define de la siguiente


forma:
n!= n* (n-1) * (n-2)* … 1

Como, a su vez

(n-1)! = (n-1)* (n-2) *… *1

Tenemos que n! se puede definir en términos de (n-1)!, para n>0, así

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:

3! = 3*2! = 3*2*1! = 3*2*1*0! = 3*2*1*1= 6

Por lo tanto, si n es distinto de cero tendremos que calcular el factorial de n-1, y


si es cero el factorial es directamente 1:

32

Algoritmos y estructuras de datos


n!= 1 si n =0
n!= n* (n-1)! Si n>=1

Observamos en este ejemplo que en la definición de factorial interviene el propio


factorial. Este tipo de definiciones en las que interviene lo definido se llaman recursivas

Un posible código sería:

Function Fac(num: integer):integer;


Begin
If num =0 then
Fac:=1
Else
Fac:= num * Fac(num - 1)
End; {function fact}

La posibilidad de que una función se llame a si misma existe, porque en Pascal el


identificador Fac es válido dentro del bloque de la propia función. Al ejecutarlo sobre el argu-
mento 4, se produce la cadena de llamadas sucesivas a Fac(4), Fac(3), Fac (2), Fac(1) y a
Fac(0), así
Fac(4) ; 4 * Fac(3)

 4 * (3 * Fac (2))

 4 * (3 * (2 * Fac(1)))

 4 * ( 3 * (2 * (1 * Fac(0))))

Y, como Fac(0) = 1, este valor es devuelto a la llamada anterior Fac(1) multiplicándose 1


* Fac(0), que a su vez es devuelto a Fac(2), donde se multiplica 2 * Fac(1) y así sucesivamente,
deshaciéndose todas las llamadas anteriores en orden inverso:

 4 * (3 * (2 * (1 * 1)))

 4 * (3 * (2 * 1))

 4 * (3 * 2)

 4 * 6

 24

¿En qué consiste la recursividad?


– En el cuerpo de sentencias del subalgoritmo se invoca al propio subalgoritmo para resolver “una
versión más pequeña” del problema original.

– Habrá un caso (o varios) tan simple que pueda resolverse directamente sin necesidad de hacer
otra llamada recursiva.

En resumen, los subprogramas recursivos se caracterizan por la posibilidad de invo-


carse a si mismos. Debe existir al menos un valor del parámetro sobre el que se hace la recursión,
llamado caso base, que no provoca un nuevo calculo recursivo, con lo que analiza y puede obtener-
se la solución; en el ejemplo del factorial, es el cero. Si este valor no existe, el cálculo no termina.
Los restantes se llaman casos recurrentes, y son aquellos para los que si se produce un nuevo cal-
culo recursivo; en el ejemplo, se trata de los valores positivos 1, 2, 3. . .

El proceso de ejecución de un subprograma recursivo consiste en una cadena de genera-


ción de llamadas (suspendiéndose los restantes cálculos) y reanulación de los mismos al termino de
la ejecución de las llamadas. Para comprender mejor el funcionamiento de un subprograma
recursivo, recordemos el proceso de llamada a un subprograma cualquiera:

Se reserva el espacio en memoria necesario para almacenar los parámetros


y los demás objetos locales del subprograma.

Se reciben los parámetros y se cede la ejecución de instrucciones al sub-


programa, que comienza a ejecutarse.

Al terminar este, se libera el espacio reservado, los identicadores locales

33
dejan de tener vigencia y pasa a ejecutarse la instrucción siguiente a la de llamada.

Programa

Begin
… Sub programa

Llamada a Begin Sub programa


subprograma …

… 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 subprograma comienza a ejecutarse normalmente y, al llegar a la llamada, se reserva espacio


para una nueva copia de sus objetos locales y parámetros. Estos datos particulares de cada ejem-
plar generado se agrupan en la llamada tabla de activación del subprograma.

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

* Este proceso de retorno finaliza con la llamada inicial.

Otros ejemplos recursivos

La sucesión de Fibonacci II

Un cálculo con definición recursiva es el de la sucesión de números de Fibonacci:


1, 1, 2, 3, 5, 8, 13, 21, 34, . . . Si llamamos
fibn al termino enésimo de la secuencia de Fibonacci, la secuencia viene descrita
recurrentemente así:

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

Algoritmos y estructuras de datos


La correspondiente función en Pascal es una trascripción trivial de esta
definición:

function Fib(num: integer): integer;

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:

Torres de Hanoi III

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

Ahora supongamos que tenemos tres discos en A y queremos pasarlos a B.


Haciendo algunos tanteos descubrimos que hay que pasar los dos discos superiores de A a C, mo-
ver el ultimo disco de A a B y por ultimo pasar los dos discos de C a B. Ya conocemos como pasar
dos discos de A a B usando C como auxiliar, para pasarlos de A a C usaremos B como varilla auxi-
liar y para pasarlos de C a B usaremos A como auxiliar:

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

Pasar 3 discos de A a B = Mover un disco de A a B

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,

Pasar n-1 discos de A a C


Pasar n discos de A a B = Mover 1 disco de A a B
Pasar n-1 discos de C a B

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.

procedure PasarDiscos(n: integer; inicial, final, auxiliar: char);


{Efecto: se pasan n discos de la aguja inicial a la final}
begin
if n > 0 then
begin
PasarDiscos (n - 1,inicial, auxiliar, final);
WriteLn('mover el disco ', n:3, ' desde ', inicial, ' a ',
final);
PasarDiscos (n - 1,auxiliar, final, inicial)
end {if}
end; {PasarDiscos}

Como ejemplo de funcionamiento, la llamada PasarDiscos(4, 'A', 'B', 'C') produce la siguiente salida:

Cuantos discos: 4 4 desde A a B


mover disco 1 desde A a C mover disco 1 desde C a B
mover disco 2 desde A a B mover disco 2 desde C a A
mover disco 1 desde C a B mover disco 1 desde B a A
mover disco 3 desde A a C mover disco 3 desde C a B
mover disco 1 desde B a A mover disco 1 desde A a C
mover disco 2 desde B a C mover disco 2 desde A a B
mover disco 1 desde A a C mover disco 1 desde C a B

Otro Ejemplo:

Imprimir el equivalente binario de un número decimal

N N MOD 2 N DIV 2
23 1 11
11 1 5
5 1 2
2 0 1
1 1 0
0

ALGORITMO DecimalAbinario(E N num)


INICIO
SI num >= 2 ENTONCES DecimalABinario(num DIV 2) Es-
cribir(num MOD 2)
SINO
Escribir (num)

36

Algoritmos y estructuras de datos


FINSI
FIN

Ventajas de la Recursión ya conocidas

• Soluciones simples, claras.


• Soluciones elegantes.
• Soluciones a problemas complejos.

Desventajas de la Recursión: INEFICIENCIA

– Sobrecarga asociada con las llamadas a subalgoritmos


– Una simple llamada puede generar un gran número de llamadas recursivas. (Fact(n) genera
n llamadas recursivas)
– ¿La claridad compensa la sobrecarga?
– El valor de la recursividad reside en el hecho de que se puede usar para resolver problemas
sin fácil solución iterativa.
– La ineficiencia inherente de algunos algoritmos recursivos.

Unidad Nº8 Registros

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.

la definición de un registro genérico en Pascal es:

type Identificador de Registro


tNombReg = record
idenCampo1: idTipo1;
idenCampo2: idTipo2;
Tipo del
...
Ca
idenCampoN: idTipoN
end; {tNombReg}

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.

Manejo de registros: acceso a componentes y operaciones

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:

alumno.nombre:= 'Pablo Achaval';


alumno.fechaNac.dia:= 2;
alumno.fechaNac.mes:= dic;
alumno.fechaNac.anio:= 1972;
alumno.nota:= Notab;

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:

procedure EscribirFicha(unAlumno: tFicha);


{Efecto: Escribe en la pantalla el contenido del registro un Alumno}

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

Algoritmos y estructuras de datos


Obsérvese que los campos fecha.mes y nota son de tipo enumerado y necesitarían dos
procedimientos especiales (EscribirMes y EscribirNota, respectivamente) para poder escribir sus
valores por pantalla.

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:

procedure LeerFicha(var unAlumno: tFicha);


{Efecto: lee del input el contenido del registro unAlumno}

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}

Como puede observarse, es incómodo estar constantemente repitiendo el identificador


unAlumno. Para evitar esta repetición Pascal dispone de la instrucción with, por ejemplo, para el
procedimiento LeerFicha se podrían utilizar dos instrucciones with anidadas de la siguiente forma:

procedure LeerFicha(var unAlumno: tFicha);


{Efecto: lee del input el contenido del registro unAlumno}

begin
with unAlumno do begin

WriteLn('Introduce el nombre del alumno:'); LeerNombre(nombre);

with fechaNac do begin

Write('Día de nacimiento: '); ReadLn(dia);


Write('Mes de nacimiento: '); LeerMes(mes);
Write('Año de nacimiento: '); ReadLn(anio)
end; {with fechaNac}

Write('Calificación: '); LeerNota(nota)


end {with unAlumno}

end; {LeerFicha}

Registros con variantes

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.

El diagrama sintáctico de la definición de un registro con variantes es el siguiente:

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}

Con la definición anterior, el procedimiento LeerFicha queda como sigue:

procedure LeerFicha(var unAlumno: tFicha);


{Efecto: lee del input el contenido del registro unAlumno}

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}

Write('Calificación: '); LeerNota(nota);


repeat
Write('Es ',nombre,' Argentino? (S/N)');
ReadLn(c)
until c in ['s','S','n','N'];

case c of
's','S' :
begin {el alumno es argentino}
Argentino:= True;
Write('DNI: '); LeerDNI(dni)

40

Algoritmos y estructuras de datos


end;
'n','N' :
begin {el alumno es extranjero}
argentino:= False;
Write('país: '); LeerPais(pais);
Write('pasaporte: '); LeerPasaporte(pasaporte)
end
end {case}
end {with unAlumno}
end; {LeerFicha}

Memoria dinámica

Unidad Nº 9 Punteros

Las estructuras de datos estudiadas hasta ahora se almacenan estáticamente en la memo-


ria física de la computadora. Cuando se ejecuta un subprograma, se destina memoria para cada
variable global del programa y tal espacio de memoria permanecerá reservado durante toda su
ejecución, se usen o no tales variables; por ello, en este caso hablamos de asignación estática de
memoria.
Esta rigidez presenta un primer inconveniente obvio: las estructuras de datos estáticas no
pueden crecer o menguar durante la ejecución de un programa. Obsérvese sin embargo que ello no
implica que la cantidad de memoria usada por un programa durante su funcionamiento sea cons-
tante, ya que depende, por ejemplo, de los subprogramas llamados. Y, más aún, en el caso de
subprogramas recursivos se podría llegar fácilmente a desbordar la memoria de la computadora.
Por otra parte, la representación de ciertas construcciones (como las listas) usando las
estructuras conocidas (concretamente los arrays) tiene que hacerse situando elementos consecuti-
vos en componentes contiguas, de manera que las operaciones de inserción de un elemento nuevo
o desaparición de uno ya existente requieren el desplazamiento de todos los posteriores para cubrir
el vacío producido, o para abrir espacio para el nuevo.
Estos dos aspectos, tamaño y disposición rígidos, se superan con las llamadas estructuras
de datos dinámicas. La definición y manipulación de estos objetos se efectúa en Pascal mediante
un mecanismo nuevo (el puntero), que permite al programador referirse directamente a la memo-
ria.
Además estas estructuras de datos son más flexibles en cuanto a su forma: arboles de
tamaños no acotados y con ramificaciones desiguales, redes (como las vías del ferrocarril por
ejemplo).

Introducción al uso de 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).

Definición y declaración de punteros

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.

Generación y destrucción de variables dinámicas

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

tiene un efecto doble:

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.

que, gráficamente, se puede expresar así:

Para destruir una variable dinámica se usa el procedimiento estándar Dispose.


La instrucción

Dispose(apCar);

42

Algoritmos y estructuras de datos


realiza las dos siguientes acciones:

1. Libera la memoria asociada a la variable referida apCar^ (dejándola disponible


para otros fines).
2. Deja indefinido el valor del puntero.

Gráficamente, esos efectos llevan a la siguiente situación:

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

Operaciones básicas con datos apuntados

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

escribiéndose en la salida la letra 'A'.

type
tApNumero = ^integer;
var
apNum1, apNum2: tApNumero;

el siguiente fragmento de programa es válido:

New(apNum1);
New(apNum2);
apNum1^:= 2;
apNum2^:= 4;
apNum2^:= apNum1^ + apNum2^;
apNum1^:= apNum2^ div 2;

Por ejemplo se pude utilizar un vector en la variable apVect de 10 elementos reales:

43
type
tVector10 = array[1..10] of real;
tApNumero = ^integer;
tApVector10 = ^tVector10;
var

apNum1, apNum2: ApNumero;


apVect: tApVector10;
i: integer;
...

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;

Por su parte, las operaciones siguientes, por ejemplo, no serán correctas:

ReadLn(apVect^); {Lectura de un vector de un solo golpe}


apNum1^:= apNum2^ + apVect^[1]; {Tipos incompatibles}

Operaciones básicas con punteros

Sólo las operaciones de comparación (con la igualdad) y asignación están permitidas entre punte-
ros.

apNum1^:= 6;
apNum2^:= 6;

se puede realizar la siguiente asignación apNum1:= apNum2

en la que se observa que ambos punteros señalan a la misma dirección, resultando ahora iguales al
compararlos:

apNum1 = apNum2

produce un resultado Verdadero y, como consecuencia, apNum1^ y apNum2^ tienen el mismo


valor, 6. Además, esta coincidencia en la memoria hace que los cambios efectuados sobre ap-
Num1^ o sobre apNum2^ sean indistintos:

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

Algoritmos y estructuras de datos


Por ejemplo, la siguiente asignación define el puntero apCar:

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.

Aplicaciones no recursivas de los punteros

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:

1. La asignación de objetos no simples en un solo paso.


2. La definición de funciones cuyo resultado no es simple.

Asignación de objetos no simples

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;

en vez de las variables pers1 y pers2. Entonces, la instrucción

p1:= p2

es casi instantánea, por consistir tan sólo en la copia de una dirección.

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:

procedure Intercambiar(var p1, p2: tApFicha);


var
aux: tApFicha;

begin
aux:= p1;
p1:= p2;
p2:= aux
end; {intercambiar}

La ordenación de vectores de elementos grandes es un problema en el que la aplicación mostrada


demuestra su utilidad. Por ejemplo, la definición

type
tListaAlumnos = array [1..100] of tFicha;

se puede sustituir por un vector de punteros,

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.

Funciones de resultado no simple

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

Una primera aproximación al diseño del programa podría ser la siguiente:

Lectura de datos: punto base, distancia y ángulo


Calculo del punto final
Escritura de resultados

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

angulo, distancia: real;


origen: tPunto;
pDestino: tApPunto;

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:

function Destino(orig: tPunto; ang, dist: real): tApPunto;


var
pPun: tApPunto;
begin
New(pPun);
pPun^.x:= orig.x + dist * Cos(ang);
pPun^.y:= orig.y + dist * Sin(ang);
Destino:= pPun
end; {Destino}

46

Algoritmos y estructuras de datos


Finalmente, si se añaden los procedimientos de lectura de datos y escritura de resultados tenemos
el siguiente programa:

Program Salto (input, output);

type
tPunto = record
x, y: real
end {tPunto};

tApPunto = ^tPunto;

var
angulo, distancia: real;
origen: tPunto;
pDestino: tApPunto;

function Destino(orig: tPunto; ang, dist: real): tApPunto;


var
pPun: tApPunto;
begin
New(pPun);
pPun^.x:= orig.x + dist * Cos(ang);
pPun^.y:= orig.y + dist * Sin(ang)
Destino:= pPun
end; {Destino}

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

Estructuras recursivas lineales:


Las listas enlazadas

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.

Esta construcción de las listas recibe el nombre de lista enlazada dinámica.

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.

Por ejemplo para la declaración de un nodo( de enteros)


Type
punteroNodo =^tiponodo;{ es la única situación en que está permitido utili-
zar un identificador tiponodo antes de ser definido}
tipoNodo = record
info:integer;
Enlace:punteroNodo
End;

Inicialización

La primera operación a realizar con una lista enlazada es la inicialización. Esta operación
constituye una lista vacía.

Procedure inicializar(var lista:tipoLista) {salida, lista lineal enlaza-


da}

Begin {tipoLista, puntero a un nodo}


L:= nil {fija el puntero del primer nodo a nil}
End;

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

3. Posteriormente, se asigna nuevoDato al campo contenido del nuevo nodo:


lista^.contenido:= nuevoDato;

48

Algoritmos y estructuras de datos


4. Y, por ultimo, se asigna la listaAux al campo siguiente del nuevo nodo para indicar los demás
elementos de la lista, es decir:

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:

procedure AnnadirPrimero(var lista: tLista; nuevoDato: tElem);


{Efecto: se añade nuevoDato al comienzo de lista}
var
listaAux: tLista;
begin

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

procedure EliminarPrimero(var lista: tLista);


{PreC.: la lista no está vacía}
{Efecto: el primer nodo ha sido eliminado}
var
listaAux: tLista;
begin
listaAux:= lista;
lista:= lista^.siguiente;
Dispose(listaAux)
end; {EliminarPrimero}
Operaciones sobre listas

A continuación se enumeran algunas de las operaciones que más frecuentemente se utilizan al


trabajar con listas:
1. Determinar el número de elementos de una lista.
2. Leer o modificar el k-ésimo elemento de la lista.
3. Insertar o eliminar un elemento en la k-ésima posición.
4. Insertar o eliminar un elemento en una lista ordenada.
5. Combinar dos o más listas en una única lista.
6. Dividir una lista en dos o más listas.
7. Ordenar los elementos de una lista de acuerdo con un criterio dado.
8. Insertar o eliminar un elemento en la k-ésima posición de una lista de acuerdo con un crite-
rio dado.
9. Buscar si aparece un valor dado en algún lugar de una lista.

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.

El primer esbozo de este procedimiento es muy sencillo:

Localizar el nodo (k-1)-ésimo de lista.


Asociar el puntero siguiente del nodo (k-1)-ésimo al nodo (k+1)-ésimo

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;

y luego se avanzará iterativamente al segundo, tercero, . . . , (k-1)-ésimo ejecutando

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:

procedure EliminarK(k: integer; var lista: tLista);


{PreC.: 1 < k · longitud de la lista}
{Efecto: se elimina el elemento k-ésimo de lista}
var
apAux, nodoSupr: tLista;
i: integer;
begin
apAux:= lista;
{El bucle avanza apAux hasta el nodo k-1}
for i:= 2 to k-1 do
apAux:= apAux^.siguiente;
{Actualizar punteros y liberar memoria}

nodoSupr:= apAux^.siguiente;
apAux^.siguiente:= nodoSupr^.siguiente;
Dispose(nodoSupr)
end; {EliminarK}

El procedimiento InsertarK

Otro procedimiento importante es el de inserción de un elemento tras cierta posición de la lista. En


particular, a continuación se implementará la inserción de un nuevo nodo justo después del k-ésimo
nodo de una lista (de longitud mayor que k). Puesto que la inserción de un nuevo nodo al comienzo
de una lista ya se ha presentado, nos restringiremos al caso k >=1.

50

Algoritmos y estructuras de datos


Pasos:
Localizar el nodo k-ésimo de lista
Crear un nuevoNodo y asignarle el contenido nuevoDato
Asociar el puntero siguiente del nodo k-ésimo a nuevoNodo
Asociar el puntero siguiente de nuevoNodo al nodo (k+1)-ésimo

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:

procedure InsertarK(k: integer; nuevoDato: tElem; var lista: tLista);


{PreC.: k ¸ 1 y lista tiene al menos k nodos}
{Efecto: nuevoDato se inserta tras el k-ésimo nodo de lista}
var
nuevoNodo, apAux: tLista;
i: integer;
begin
apAux:= lista;
{El bucle avanza apAux hasta el nodo k}
for i:= 1 to k-1 do
apAux:= apAux^.siguiente;
{Actualización de punteros}
New(nuevoNodo);
nuevoNodo^.contenido:= nuevoDato;
nuevoNodo^.siguiente:= apAux^.siguiente;
apAux^.siguiente:= nuevoNodo
end; {InsertarK}

Implementación dinámica de una lista con enlace simple

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:

• Una componente que apunte al primer elemento de la lista


• Una componente que apunte al último elemento de la lista
• Una componente que nos dé la longitud de la lista.

De este modo, la estructura de datos utilizada quedará así:

TYPE
Posicion = ^Elemento;

Elemento = RECORD
info: <TipoBase>
sig: Posicion
END;

TipoLista = RECORD
longitud: INTEGER;
primero, ultimo: Posicion
END;

Veamos como se implementan las distintas operaciones

PROCEDURE CrearLista (VAR L: TipoLista);


BEGIN
L.longitud := 0;
L.primero := NIL;
L.ultimo := NIL
END;

Al crear una lista, su longitud será 0 y no estarán definidos ni su primer ni su último elemento.

FUNCTION ListaVacia(L: TipoLista):BOOLEAN;


BEGIN
ListaVacia := (L.longitud=0)
END;

Se considerará que la lista está vacía cuando su longitud sea 0.

FUNCTION Primero (L:TipoLista):Posicion;


BEGIN
Primero := L.primero
END;

El primer elemento de la lista es el apuntado por el campo primero.


FUNCTION Ultimo (L: TipoLista): Posicion;
BEGIN
Ultimo := L.ultimo
END;

El último elemento de la lista es el apuntado por el campo ultimo. Si no hubiésemos inclui-


do este campo en la estructura de datos TipoLista, sería necesario recorrer toda la lista desde su
primer elemento para devolver la posición del último. En las dos funciones anteriores no hemos
considerado el caso en el que la lista estuviese vacía de un modo especial. En este caso, por el
modo en que el resto de subprogramas tratan los distintos campos de la estructura, la posición
devuelta será NIL.
FUNCTION Siguiente ( L: TipoLista; p: Posicion): Posicion;

52

Algoritmos y estructuras de datos


BEGIN
Siguiente := p^.sig
END;
En una implementación completamente libre de errores deberíamos haber contemplado el
caso en el que la posición p pasada como argumento no correspondiese a un elemento de la lista.
En ese caso deberíamos haber devuelto algún tipo de error. Sin embargo, para no complicar el
código, vamos a suponer que al usar esta función, siempre se pasa una posición que apunta a uno
de los elementos de la lista. Este elemento puede haberse encontrado por ejemplo usando la fun-
ción Buscar que veremos más adelante.
FUNCTION Longitud ( L : TipoLista ): INTEGER;
BEGIN
Longitud := L.longitud
END;

Al disponer de un campo longitud en la estructura que representa la lista, es muy sencillo


implementar esta operación. Si no hubiésemos incluido este campo, habría sido necesario recorrer
toda la lista y contar sus componentes.
FUNCTION Anterior (L: TipoLista; p:Posicion): Posicion;
VAR
aux: Posicion;
BEGIN
IF p = Primero(L) THEN
aux := NIL
ELSE BEGIN
aux := Primero (L);
WHILE (Siguiente(L,aux) <> p) DO
aux := Siguiente(L,aux)
END;
Anterior := aux
END;

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.

PROCEDURE Dato (L: TipoLista; p: Posicion; VAR d: TipoBase);


BEGIN
d := p^.info
END;

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;

Supongamos que partimos de una lista como la siguiente:

Para almacenar un elemento al final debemos recorrer los siguientes pasos:

• En primer lugar creamos un nuevo nodo mediante la operación new(aux).


• A continuación rellenamos los dos campos del nodo. El campo info con el valor d pasado como
parámetro. Dado que el nuevo elemento va a ser el último de la lista, el campo sig se rellena con
NIL.

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

PROCEDURE InsAntes (VAR L: TipoLista; p: Posicion; d: TipoBase );


VAR
aux1,aux2: Posicion;

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

Algoritmos y estructuras de datos


BEGIN { cualquier posición }
aux2 := Anterior (L, p);
aux2^.sig := aux1;
aux1^.sig := p
END;

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

2. Inserción al principio de la lista

2. Inserción a partir del primer elemento


En este caso debemos insertar el nuevo nodo entre el apuntado por p y el anterior. Esto
es, debemos hacer que el anterior apunte al nuevo nodo y que el nuevo nodo apunte a p.
Para ello, lo primero que haremos es localizar el nodo anterior a p utilizando la operación
Anterior(L,p).

PROCEDURE InsDepues (VAR L: TipoLista; p: Posicion; d: TipoBase );


VAR
aux1: Posicion;

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

De nuevo debemos comenzar por crear el nuevo nodo y almacenar la información en su


campo info, y debemos finalizar incrementando en uno la longitud de la lista.

1. Si la lista está vacía se opera igual que en InsAntes.


2. Si queremos insertar después del último elemento.

PROCEDURE Modificar (VAR L: TipoLista; p: Posicion; d: TipoBase);


BEGIN
p^.info := d
END;

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.

PROCEDURE Borrar ( VAR L: TipoLista; p: Posicion );


VAR
aux: Posicion;

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.

1. Si el elemento a borrar es el primero

56

Algoritmos y estructuras de datos


Además, si el nodo a borrar es el último de la lista, debemos hacer que el campo
L.ultimo apunte al anterior (aux).

Implementación dinámica como lista doblemente enlazada

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.

Para implementar en Pascal este tipo de listas, utilizaremos las siguientes


definiciones:

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.

FUNCTION Anterior (L : TipoLista; p: Posicion ):Posicion;


BEGIN
Anterior := p^.ant
END;

PROCEDURE InsAntes( VAR L: TipoLista; p: Posicion; d: TipoBase );


VAR
aux : Posicion;

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;

PROCEDURE InsDespues( VAR L : TipoLista; p: Posicion; d: TipoBase );


VAR
aux: Posicion;

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;

PROCEDURE Borrar ( VAR L: TipoLista; p: Posicion );


VAR
aux1, aux2 : Posicion;

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

Algoritmos y estructuras de datos


La estructura de datos que podemos utilizar en Pascal para implementar una multilista como la
anterior es la siguiente
TYPE
Alumno = RECORD
nombre, DNI: String;
nota: REAL
END;

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.

Implementación dinámica del tipo Pila

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:

- El valor del elemento de la pila (de TipoBase).


- Un puntero al siguiente nodo de la pila (contiene la dirección de memoria de siguiente nodo de la
pila).

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 definición anterior es recursiva, ya que la definición de TipoPila depende de la de Elem-


Pila, y la definición de este último depende de la definición de TipoPila, pues lo utiliza en uno de
sus campos. Debido a este hecho, en ocasiones este tipo de estructura, y otras que veremos en
temas posteriores (colas, árboles, ...), se denominan estructuras de datos recursivas.
Utilizando esta implementación, la estructura de datos pila será como la representada en la siguien-
te figura:

PROCEDURE CrearPila (VAR p: TipoPila);


BEGIN
p := NIL
END;

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.

PROCEDURE Tope (p: TipoPila; VAR e: TipoBase; VAR error: BOOLEAN);


BEGIN
IF PilaVacia(p) THEN
error := true
ELSE
BEGIN
error := false;
e := p^.info
END
END;

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

Algoritmos y estructuras de datos


BEGIN
new(q);
q^.info := e;
q^.sig := p;
p := q
END;

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:

PROCEDURE Desapilar (VAR p: TipoPila; VAR error: BOOLEAN);


VAR
q: TipoPila;
BEGIN
IF PilaVacia(p) THEN
error := true
ELSE
BEGIN
error := false;
q := p;
p := p^.sig;
dispose(q)
END
END;

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.

Estructura de datos Cola

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

Una cola es un conjunto ordenado de elementos homogéneos, en el cual los elementos se


eliminan por uno de sus extremos, denominado cabeza, y se añaden por el otro extremo, denomi-
nado final. Las eliminaciones y añadidos se realizan siguiendo una política FIFO.

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:

- Añadir: Añade un elemento al final de la cola.


- Eliminar: Elimina un elemento de la cabeza de la cola.

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

Algoritmos y estructuras de datos


TipoCola = RECORD
cabeza, final : TipoPuntero
END;

Gráficamente:

Con esta definición se obtiene la siguiente implementación para las operaciones:

PROCEDURE CrearCola ( VAR q: TipoCola );


BEGIN
q.cabeza := NIL;
q.final := NIL
END;

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.

FUNCTION ColaVacia ( q: TipoCola ): BOOLEAN;


BEGIN
ColaVacia := (q.cabeza=NIL) AND (q.final=NIL)
END;
Consideraremos que la cola está vacía cuando tanto la cabeza como el final apunten a NIL. Este
caso se dará nada más ser creada y cuando se acabe de eliminar su último elemento.

PROCEDURE Cabeza (q:TipoCola; VAR e:<Tipobase>; VAR error: BOOLEAN);


BEGIN
IF ColaVacia(q) THEN
error := true
ELSE
BEGIN
error := false;
e := q.cabeza^.info
END
END;
Para conocer la cabeza de la cola basta con acceder al nodo apuntado por q.cabeza. El
campo info de este nodo contendrá el elemento de la cola deseado. Esta operación debe devolver
un error cuando la cola se encuentre vacía.

PROCEDURE Eliminar ( VAR q: TipoCola; VAR error: BOOLEAN);


VAR
aux: TipoPuntero;
BEGIN
IF ColaVacia(q) THEN
error := true
ELSE
BEGIN
error := false;
aux := q.cabeza;
q.cabeza := aux^.sig;

IF ( q.cabeza = NIL ) THEN { hemos vaciado la cola }


q.final := NIL;
dispose(aux)
END
END;

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.

• Finalmente se libera la memoria correspondiente al nodo que ocupa la cabeza.

• Al salir del procedimiento se libera la memoria de la variable local aux.

Al implementar la rutina de eliminado hemos de tener en cuenta dos casos particulares.

• Si la cola estaba vacía, el procedimiento devolverá un error


• Si la cola contenía un solo elemento, tras eliminarlo quedará vacía. Cuando nos encontremos en
este caso, el procesamiento normal nos llevará a que q.cabeza valga NIL. Cuando se produzca este
hecho debemos hacer que también q.final valga NIL.

PROCEDURE Añadir ( VAR q: TipoCola ; e: <TipoBase>);


VAR
aux: TipoPuntero;
BEGIN
new(aux);
aux^.info := e;
aux^.sig := NIL;
IF ColaVacia(q) THEN
q.cabeza := aux
ELSE
q.final^.sig := aux;
q.final := aux
END;

• Para añadir un nuevo elemento, las tres primeras instrucciones del procedimiento crean y rellenan
un nuevo nodo apuntado por aux:

64

Algoritmos y estructuras de datos


• A continuación, si la cola estaba vacía, se hace que la cabeza apunte al nuevo nodo. Si la cola no
estaba vacía, el último nodo de la cola debe apuntar al que se va a añadir. En ambos casos, se
hace apuntar el final de la cola al nuevo nodo.

Ejemplo de cola.

Program ejemplo;
uses crt;
TYPE
TipoPuntero = ^ElemCola;

ElemCola = RECORD
info: integer;
sig: TipoPuntero
END;

TipoCola = RECORD
cabeza, final : TipoPuntero
END;

PROCEDURE CrearCola ( VAR q: TipoCola );


BEGIN
q.cabeza := NIL;
q.final := NIL
END;

FUNCTION ColaVacia ( q: TipoCola ): BOOLEAN;


BEGIN
ColaVacia := (q.cabeza=NIL) AND (q.final=NIL)
END;

PROCEDURE Cabeza (q:TipoCola; VAR e:integer; VAR error: BOOLEAN);


BEGIN
IF ColaVacia(q) THEN
error := true
ELSE
BEGIN
error := false;
e := q.cabeza^.info
END
END;

PROCEDURE Eliminar ( VAR q: TipoCola; VAR error: BOOLEAN);


VAR
aux: TipoPuntero;
BEGIN
IF ColaVacia(q) THEN
error := true
ELSE
BEGIN
error := false;
aux := q.cabeza;
q.cabeza := aux^.sig;
IF ( q.cabeza = NIL ) THEN { hemos vaciado la cola }
q.final := NIL;
dispose(aux)
END
END;

PROCEDURE Aniadir ( VAR q: TipoCola ; e: integer);


VAR
aux: TipoPuntero;
BEGIN
new(aux);
aux^.info := e;
aux^.sig := NIL;
IF ColaVacia(q) THEN
q.cabeza := aux
ELSE
q.final^.sig := aux;
q.final := aux
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);

writeln(' 1. a¤adir ');


writeln(' 2. sacar');
writeln(' 3. mostrar');
op:= readkey;
case op of
'1': begin
writeln('elemento?'); readln(el);

aniadir(col,el);

end;
'2': begin

eliminar(col,error);
end;
'3':begin
mostrar(col);
end;
end;
until op='5';
end.

Estructuras de datos no lineales. Árboles binarios

Introducción. Terminología básica y definiciones


Al igual que ocurría con las estructuras de datos vistas en los temas anteriores, todo el
mundo tiene claro el concepto de árbol, al menos en su aspecto botánico. Sin embargo, los árboles
no son sólo eso de lo que estamos rodeados cuando nos perdemos en un bosque, sino que también
se usan en otros muchos ámbitos. Así por ejemplo, todos hemos manejado alguna vez el concepto
de árbol genealógico, o hemos visto clasificaciones jerárquicas como las del reino animal. En todos
esos casos manejamos el concepto de árbol.
Centrándonos en el mundo de la informática, los árboles se utilizan en distintos ámbitos.
Por ejemplo, al organizar la información para facilitar la búsqueda en un disco rígido, utilizamos una
estructura de directorios y subdirectorios en forma de árbol. También se usan los árboles asociados
a distintos esquemas para el desarrollo de los algoritmos, tales como la programación dinámica, la
ramificación y poda, el esquema divide y vencerás, etc.
Si nos referimos a estructuras de datos, ya dijimos en el tema anterior que las pilas, colas y
listas son estructuras lineales, puesto que en todas ellas cada elemento tiene un único elemento
anterior y un único elemento posterior. Pero, además, existe estructuras de datos no lineales, en
las que esta restricción desaparece. Esto es, en estas estructuras cada elemento puede tener varios
anteriores y/o varios posteriores.

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

Algoritmos y estructuras de datos


árboles no son más que un tipo especial de grafo en el que cada elemento puede tener varios pos-
teriores, pero tan sólo puede tener un elemento anterior. Tanto grafos como árboles son estructu-
ras no lineales.
Si añadimos a los árboles la restricción de que cada elemento puede tener un solo poste-
rior, llegamos a las estructuras lineales, y más concretamente a las listas. Así pues, las listas no son
más que un caso particular de los árboles. En este punto, si añadimos ciertas restricciones de acce-
so a las listas llegamos a las colas o a las pilas. Por lo tanto, tanto colas como pilas, son tipos parti-
culares de listas. En definitiva, gráficamente podemos ver la relación entre las distintas estructuras
comentadas del siguiente modo:

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:

Veamos algunas definiciones básicas:

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

• Bosque colección de dos o más árboles. Un ejemplo de bosque sería el siguiente:

Árboles binarios. Recorrido

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

Un Árbol binario es aquel que


a) es vacío, ó
b) está formado por un nodo cuyos subárboles izquierdo y derecho son a su vez árboles binarios.

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

Algoritmos y estructuras de datos


La figura anterior representa un árbol binario completo. En el nivel 1 tenemos 20 = 1 nodos, en el
nivel 2 tenemos 21 = 2 nodos y en el nivel 3 tenemos 22=4 nodos. En total el árbol es de altura 3
y por tanto contiene 23-1 = 7 nodos.

Implementaciones del Árbol binario

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:

• La información de un tipobase dado contenida en el nodo.


• Un enlace al hijo derecho (raíz del subárbol derecho)
• Un enlace al hijo izquierdo (raíz del subárbol izquierdo)

Gráficamente:

Implementación dinámica mediante punteros

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.

PROCEDURE CrearArbol (VAR A:TArbol);


BEGIN
A := NIL
END;
Para crear un árbol simplemente hacemos que el puntero a su nodo raíz apunte a NIL.

FUNCTION ArbolVacio (A:TArbol): BOOLEAN;


BEGIN
ArbolVacio := (A = NIL)

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.

PROCEDURE SubDer (A: TArbol; VAR subd: TArbol);


BEGIN
subd := A^.der
END;
Para acceder al subárbol derecho de un árbol, basta con acceder al puntero al hijo derecho
de su nodo raíz.

PROCEDURE DatoRaiz (A: TArbol; VAR d: <tipobase>);


BEGIN
d := A^.info
END;
Para acceder al dato contenido en el nodo raíz de un árbol, basta con acceder al campo
info del registro que lo representa.
Con la implementación elegida, y tal y como se definió la operación ConstArbol, los nodos
hoja se caracterizarán por tener los punteros al subárbol izquierdo y al subárbol derecho con valor
NIL.
Dado que en esta implementación hemos definido el tipo TArbol como un puntero, Pascal
permite que este sea devuelto por una función. De este modo, los procedimientos ConstArbol,
SubDer e SubIzq podrían haberse implementado como funciones. Asimismo, si el tipo base de la
información contenida en cada nodo es escalar, también podemos convertir el procedimiento Dato-
Raiz en una función.

Recorrido de un Árbol binario

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:

• acceder a la información de un nodo dado,


• acceder a la información del subárbol izquierdo de dicho nodo,
• acceder a la información del subárbol derecho de dicho nodo.

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

Algoritmos y estructuras de datos


• Postorden: primero se accede a la información del subárbol izquierdo, después a la del subárbol
derecho y, por último, se accede a la información del nodo.

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:

PROCEDURE Preorden (A: TArbol);


BEGIN
IF (NOT ArbolVacio(A)) THEN
BEGIN
manipula_info(DatoRaiz(A));
Preorden(SubIzq(A));
Preorden(SubDer(A))
END
END;

PROCEDURE Inorden (A: TArbol);


BEGIN
IF (NOT ArbolVacio(A)) THEN
BEGIN
Inorden(SubIzq(A));
manipula_info(DatoRaiz(A));
Inorden(SubDer(A))
END
END;

PROCEDURE Postorden (A: TArbol);


BEGIN
IF (NOT ArbolVacio(A)) THEN
BEGIN
Postorden(SubIzq(A));
Postorden(SubDer(A))
manipula_info(DatoRaiz(A));
END
END;

En esta implementación el recorrido de cada nodo se realiza mediante la operación manipu-


la_info. Cambiando esta operación podemos por ejemplo escribir toda la información almacenada,
sumar los posibles valores numéricos contenidos en los distintos nodos, etc.
Veamos como funciona por ejemplo el procedimiento recursivo Preorden mediante una
traza. Para ello recorreremos el siguiente árbol, en el que hemos dado nombre a todos los subárbo-
les que lo componen.

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

Algoritmos y estructuras de datos


Búsqueda de un elemento

La operación de búsqueda en un árbol binario de búsqueda es bastante sencilla de entender. Su-


pongamos que buscamos un elemento x en el árbol. Lo primero que haremos será comprobar si se
encuentra en el nodo raíz. Si no es así, si el elemento buscado es menor que el contenido en el
nodo raíz sabremos que, de estar en el árbol, se encuentra en el subárbol izquierdo. Si el elemento
buscado es mayor que el contenido en el nodo raíz sabremos que, de estar en el árbol, se encuen-
tra en el subárbol derecho. Para continuar la búsqueda en el subárbol adecuado aplicaremos recur-
sivamente el mismo razonamiento.

Por lo tanto, el esquema del algoritmo BuscarNodo será el siguiente:

1. Si el valor del nodo actual es igual al valor buscado, lo hemos encontrado.


2. Si el valor buscado es menor que el del nodo actual, deberemos inspeccionar el subárbol izquier-
do.
3. Si el valor buscado es mayor que el del nodo actual, deberemos inspeccionar el subárbol dere-
cho.

FUNCTION BuscarNodo (A: TArbol; x:<tipobase>):TArbol;


VAR
p: TArbol;
enc: BOOLEAN;
BEGIN
p := A;
enc := false;
WHILE (NOT enc) AND (NOT ArbolVacio(p)) DO
BEGIN
enc := (DatoRaiz(p) = x);
IF NOT enc THEN
IF (x < DatoRaiz(p)) THEN
p := SubIzq(p)
ELSE
p := SubDer(p)
END;
BuscarNodo := p
END;
Si al acabar la operación el resultado devuelto es NIL, interpretaremos que no hemos encontrado el
elemento buscado.

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:

1. Creación del nuevo nodo


2. Búsqueda de su posición correspondiente en el árbol. Se trata de encontrar la posición que le
corresponde para que el árbol resultante siga siendo de búsqueda.
3. Inserción en la posición encontrado. Se modifican de modo adecuado los enlaces de la estructu-
ra.

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.

PROCEDURE InsertarNodo (VAR A: TArbol; x:<tipobase>);


VAR
p, aux, padre_aux: TArbol;
BEGIN
{ Crear el nuevo nodo }
new(p);
p^.info := x;
p^.izq := NIL;
p^.der := NIL;
IF ArbolVacio(A) THEN
A := p
ELSE
BEGIN { Buscar el lugar que le corresponde }
aux := A;
WHILE NOT ArbolVacio(aux) DO
BEGIN
padre_aux := aux;
IF x <= DatoRaiz(aux) THEN
aux := SubIzq(aux)
ELSE
aux := SubDer(aux)
END;

{ Insertar el nuevo nodo }


IF x <= DatoRaiz(padre_aux) THEN
padre_aux^.izq := p
ELSE
padre_aux^.der := p
END
END;

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

Algoritmos y estructuras de datos


remos un puntero aux que ira recorriendo una rama del árbol. Durante este recorrido, cuando nos
encontremos en un nodo cualquiera, pasaremos a su hijo izquierdo si el valor a insertar es menor
que el del nodo actual, y pasaremos a un hijo derecho si el valor a insertar es mayor que el del
nodo actual.
Consideremos que hemos llegado al final del recorrido cuando alcancemos una hoja.
Si consideramos el nodo actual como raíz de un árbol, esta condición se dará cuando el-
subárbol al que pretendemos acceder esté vacío.
En todo momento durante el recorrido debemos mantener no sólo un puntero al nodo ac-
tual, aux, sino también un puntero a su padre, padre_aux. Esto es así porque para insertar el nue-
vo nodo debemos enlazarlo con su padre, y esto sólo es posible desde el nodo padre.
Una vez encontrada la posición adecuada para la inserción, enlazaremos el nuevo nodo con
su padre utilizando el puntero adecuado en función de su valor.

Eliminación de un elemento

La eliminación de un nodo de un árbol binario de búsqueda es más complicada que la inserción,


puesto que puede suponer la recolocación de varios de sus nodos. En líneas generales un posible
esquema para abordar esta operación es el siguiente:

1. Buscar el nodo que se desea borrar manteniendo un puntero a su padre.

2. Si se encuentra el nodo hay que contemplar tres casos posibles:

a. Si el nodo a borrar no tiene hijos, simplemente se libera el espacio que ocupa

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.

Veamos gráficamente varios ejemplos de eliminación de un nodo:

El siguiente código representa una posible implementación de esta operación

PROCEDURE EliminarNodo(VAR A:TArbol; x:<tipobase>; VAR enc:BOOLEAN);


VAR
p, padre_p, sust, p_sust: TArbol; enc:BOOLEAN;

BEGIN

75
{ Busqueda del elemento a eliminar }
p := A;
enc := false;

WHILE (NOT enc) AND (NOT ArbolVacio(p)) DO


BEGIN
enc := (DatoRaiz(p) = x);

IF NOT enc THEN


BEGIN
padre_p := p;
IF (x <= DatoRaiz(p)) THEN
p := SubIzq(p)
ELSE
p := SubDer(p)
END
END;
{ Eliminacion del nodo si se ha encontrado }
IF enc THEN
BEGIN
IF ArbolVacio(SubIzq(p)) THEN {a ó b - sin hijos o el derecho}
sust := SubDer(p)
ELSE IF ArbolVacio(SubDer(p)) THEN { caso b - un solo hijo }
sust := SubIzq(p)
ELSE
BEGIN { caso c - nodo con los dos hijos }
p_sust := p;
sust := SubIzq(p);

WHILE NOT ArbolVacio(SubDer(sust)) DO


BEGIN
p_sust := sust;
sust := SubDer(sust)
END;
IF p_sust = p THEN
p_sust^.izq := SubIzq(sust)
ELSE
p_sust^.der := SubIzq(sust);
sust^.izq := SubIzq(p);
sust^.der := SubDer(p);
END;
IF p=A THEN
A := sust
ELSE IF (p = SubIzq(padre_p)) THEN
padre_p^.izq := sust
ELSE
padre_p^.der := sust;
dispose(p)
END
END;

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

Algoritmos y estructuras de datos


pre a sucesivos hijos derechos, mientras estos existan. El código utilizado para llevar a cabo este
proceso es el siguiente:

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.

En el ejemplo 1, cuando el nodo sustituto es hijo del eliminado (p_sust=p), el subárbol a


salvaguardar deberá colgarse de la rama izquierda de p_sust. En el ejemplo 2, cuando no se cum-
ple la condición anterior, el subárbol a salvaguardar deberá colgarse de la rama derecha de p_sust,
tal y como se ve en el paso 1 de la siguiente figura. El código utilizado para realizar este enlace es
el siguiente:

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

Las constantes definidas para los colores son:


Colores para primer plano y fondo: Colores para primer plano:

Constante Valor Color Constante Valor Color


Black 0 Negro Dark gray 8 Gris oscuro
Blue 1 Azul Light Blue 9 Azul claro
Green 2 Verde Light Green 10 Verde claro
Cyan 3 Cyan Light Cyan 11 Cyan claro
Red 4 Rojo Light Red 12 Rojo claro
Magenta 5 Magenta Light Magenta 13 Magenta claro
Brown 6 Marrón Yellow 14 Amarillo
Light Gray 7 Gris claro White 15 Blanco

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.

Cuando el contenido de esta variable es True se encuentra activada la terminación de un programa


por medio de las teclas Crtl-Break. Si se cambia su valor a False se desactiva esta opción.

Su valor por defecto es True.

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

Algoritmos y estructuras de datos


Esta variable contiene las coordenadas de la esquina inferior derecha de la ventana de texto activa
definida por el procedimiento Window.

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

Algoritmos y estructuras de datos


Los modos válidos de pantalla de texto son:

Constante Valor Modo de video


BW40 0 40x25 Blanco y negro en tarjeta de color
CO40 1 40x25 Color
BW80 2 80x25 Blanco y negro en tarjeta de color
CO80 3 80x25 Color
Mono 7 80x25 Monocromático

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.

Las siguientes son las funciones de la unidad CRT:

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

WhereX devuelve el número de la columna donde se encuentra el cursor.

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

Utilizaremos archivos de texto, por su fácil manejo.

Los archivos de texto llevan un tratamiento especial para su lectura/escritura. En primer,


lugar utilizaremos una variable de tipo Text, la cual sólo nos permite manejar archivos de
texto. Existen básicamente dos formas de abrir un archivo: como lectura o como escritura.

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.

// Lee el contenido y lo muestra en pantalla


Reset(F); // Noten que no hace falta asignarlo otra vez
ReadLn(F, b, a);
ReadLn(F, s);
Close(F);
WriteLn('a = ', a);
WriteLn('b = ', b);
WriteLn('s = ', s);
End.

Lo que vemos cuando ejecutamos este programa es:

a = 278
b = 314
s = pi = 314

Trabajo Práctico Nº 1

1.Identifique los errores de sintaxis en las siguientes líneas de código

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.

f) Program ll; b) Program desconocido;

82

Algoritmos y estructuras de datos


Var var
Y,x: integer; a , b , c : char;
Begin begin
a:= ‘a’ ;
y:=3;
x:=y+1; b:= ‘c’ ;
writeln(x,’ ‘,y); c:= a ;
y:=y+1; writeln(a, b, c, ‘c’) ;
x:=3; readln;
writeln(x,’ ‘,y); end.
Readln;
End.

4. Escribir en Pascal los algoritmos que permitan realizar las tareas

1) Hallar la superficie de un triangulo conociendo la base y la altura


2) Calcular el sueldo de un operario conociendo la cantidad de horas que trabajó en el mes y
el valor de la hora
3) Dado el radio de una esfera calcular el volumen

5. Dado el siguiente fragmento de programa que supuestamente convierte temperaturas de grados


Celsius a grados Fahrenheit

Program celsiusaFahrenheit;
var
c,f : real; Begin
c:=20
f:= (9/5)*c + 32.0 ; Writeln (f);
End.

Indicar

a) Qué valor se asigna a f


b) Qué quiso hacer el programador
c) Qué está ocurriendo realmente
d) Cómo reescribir el código para realizar la operación correctamente.

6. Determinar el valor de las siguientes expresiones aritméticas:

15 div 12 15 mod 12

24 div 12 24 mod 12

123 div 100 123 mod 100

200 div 100 200 mod 100

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

Escribir en Pascal los programas que se detallan a continuación.

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:

Temp. > 28 :Waterpolo


28.>-= Temp. >= 20 : Surf
20.>-= Temp. >= 15 :futbol
15.>-= Temp. >= 5 :ajedrez
-5 >= Temp. :Snowboard

3.- Dados tres números hallar el mayor.

4.- Calcular la suma de los N primeros números naturales.

5.- Dado un número entero decir si:


a) es par o impar;
b) es mayor, menor o igual a cero.

6.- Dado un mes escribir la cantidad de días de dicho mes.

7.- Escribir las tablas de multiplicar del número 1 al número 9 de los primeros 15 números.

8.- De cada uno de los 30 alumnos de un curso se tiene el nombre y la cantidad de


inasistencias en el año. Hacer una lista con los nombres acompañados de la leyenda
“REGULAR” o “LIBRE”. (Un alumno queda libre cuando posee más de 20 inasistencias).
Calcular la cantidad de Regulares y Libres.

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

Algoritmos y estructuras de datos


16.- Una empresa ha decidido dar a sus empleados una gratificación adicional que depende de las
horas extras y ausencias.
Sea Horas= Horas Extras – (2/3)* ausencias. La gratificación G se calcula de la siguiente manera:
Horas <= 10 G: 100
10< horas <=20 G: 200
20<horas<=30 G: 300
30<horas <=40 G: 400
40<horas G: 500
Diseñar un programa que lea el nombre de un empleado, las horas extras trabajadas y las horas de
ausentismo, y determine el monto de loa gratificación que corresponde.

17.- Sea A,B,C y MAXIMO cuatro variables numéricas

a) ¿Qué hace la siguiente estructura de decisión?

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.

b) Si a, b y c tienen el mismo valor, ¿Cuántas asignaciones se efectuarán ?


c) ¿Cómo modificaría este algoritmo para realizar una sola asignación cuando a,b, y c tienen el
mismo valor?

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.

Nota: recuerde que V =5, X=10, L=50, C=100, D=500, M= 1000.

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%

1801- 2000 10%

2001 – 2600 5%

Mas de 2600 No hay

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.

25.- Calcular la suma de los primeros 1000 múltiplos de 2.

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.

Trabajo Práctico Funciones

1. Escribir un programa que lea una cadena de caracteres y la visualice en un cuadro

**********
*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

Algoritmos y estructuras de datos


8. Escribir un programa que permita deducir si un número N es primo, apoyándose en una función
llamada Primo.

9.- Explicar que realiza el siguiente código de programa.


Hacer prueba de escritorio con las siguientes palabras: verano, Neuquen

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;

cont:=length(palabra) div 2;{Break}


end;
cont:=cont+1;
end;
if pal =true then
writeln('Si es Palindromo')
else
writeln('No es palindromo');
readkey;
end.

10.- Indicar que valores imprime el siguiente procedimiento:

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;

Begin {programa principal}

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?

11.- Interpretar que hace el siguiente programa

87
program ejemplo;
uses
crt;
var
b:real;

Function a(n: integer) :real;


Var
I:Integer;
s:real;
Begin
s:=1;
For i:=1 to n do
s:=(s*i)+s;
a:=s
End;

Begin {programa principal}


b:=a(3);
clrscr;
writeln(' el valor es: ', b:3:2);
readkey;
end.

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

Guía de trabajos Prácticos. Vectores y Matrices

1) Leer un vector llamado Z de N elementos. A partir de su lectura calcular:

a) Generar un vector llamado POSITIVO con la cantidad y el promedio de elementos po-


sitivos.
b) Generar un vector llamado NEGATIVO con la cantidad y el promedio de elementos
negativos.
c) Generar un vector llamado ZERO con la cantidad y el promedio de elementos igual a 0.

2) Una compañía almacena la información relacionada a sus proveedores en los arreglos:

- 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

Algoritmos y estructuras de datos


3) Escribir un programa que lea un vector A de N elementos (N es un dato entero suministrado por
el usuario). Una vez leído el vector, el programa debe permitir al usuario elegir a través de un
menú la ejecución de las siguientes opciones:

a) Volver a leer los datos del vector


b) Calcular el elemento mayor y menor del vector
c) Calcular la suma de los elementos que componen el vector (ΣA[i]=A[1]+A[2]+…+A[N])
d) Calcular el promedio de los elementos que componen el vector (ΣA[i]/N)
e) Calcular el producto de los elementos que componen el vector
f) Crear un nuevo vector que contenga los elementos del array transpuestos, es decir, B[1] contie-
ne el elemento A[N], B[2] contiene el elemento A[N-1], …, B[N] contiene el elemento A[1]
g) Crear un nuevo vector que contenga los elementos del vector A pero con una posición corrida,
es decir, B[1] contiene el elemento A[2], B[2] contiene el elemento A[3], …, B[N] contiene el ele-
mento A[1]
h) Crear un nuevo vector que contenga los elementos del vector A pero con M posiciones corridas
(siendo M<N), es decir, B[1] contiene el elemento A[M+1], B[2] contiene el elemento A[M+2], …
i) Salir del programa

4) Se tiene un listado con los siguientes datos:

número de alumno (1 a n ) (filas)


número de materia (1 a m ) (columnas)
nota (0 a 10).

a) El mismo número de alumno y de materia puede aparecer más de una vez.


b) El listado no está ordenado, ni necesariamente completo. Esto último quiere decir que puede
ser que un alumno no haya cursado una o más materias, y por lo tanto no existan los datos
correspondientes en el listado.

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.

6) Se posee una matriz de F filas y C columnas.

a.- Asignarle valores a todos sus elementos teniendo en cuenta que cada elemento a[i,j] está defi-
nido como

si i*j es par el valor que se le asigna a la posición es i+j

si i*j es impar el valor que se le asigna a la posición es i-j.

Ejemplo: si f=2 y c=4

b.- Imprimir la matriz.

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.

9.- Leer una matriz de F filas y C columnas. (F =C)

a.- Calcular el elemento Minimo de la matriz

b.- Calcular el promedio de cada una de las filas.

c.- Calcular el promedio de los elementos de la diagonal principal

d.- Calcular el promedio de los elementos de la diagonal secundaria.

e.- A cada elemento par de la matriz guardarlo en un vector llamado Pares.

Trabajo Práctico Recursividad.

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.

4.- ¿Qué hace el siguiente procedimiento?

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

5.- ¿Qué hace el siguiente procedimiento?


Procedure p2 ( a, b: integer );
begin
if a MOD b <> 0 then
begin
writeln ( a );
p2 ( a + 1, b );
end
else writeln ( 'Fin' )
end;

¿Qué cambiaría al eliminar el último else del programa?

Procedure p3 ( a, b: integer );
begin
if a > 0 then p3 ( a - 1, b + a )
else writeln ( b )
end;

90

Algoritmos y estructuras de datos


6.- Escribir un programa que utilice una función recursiva EscribeBlancos(n) que imprima n caracte-
res blancos consecutivos.
7.- Diseñar un algoritmo recursivo que imprima los dígitos de un número decimal en orden inverso.
8.- Escribe una función recursiva que devuelva la suma de los valores almacenados en un array de
enteros.
9.- Escribe un programa recursivo que calcule la suma de los primeros N números pares naturales.
10.- Implemente un programa que utilice una función recursiva que imprima por pantalla los
valores desde 1 hasta el número introducido desde teclado por el usuario.
11.- Dado un vector de n números enteros, diseñar un algoritmo recursivo que dado el vector de-
vuelva:
· La posición del último número par que aparece en el vector
· El valor 0 si ningún elemento del vector es par

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.

Trabajo Práctico Registros

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.

Trabajo práctico Punteros:

1 .- Que realiza el siguiente programa.


a)
Program puntero;
Type
Ptrint= ^integer;
Var
I,j:ptrint;
N: integer;
Begin
New(i);
New(j);
N:=5;
I^:=n;
Writeln(i^);
J:=i;
J^:= -7;
Writeln(i^);
End.

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:

for i:= 1 to n do begin


New(p);
p^:= i
end

92

Algoritmos y estructuras de datos


(a) ¿Cual es el efecto de ejecutarla?
(b) ¿Cual es el efecto de añadir las siguientes instrucciones tras la anterior?

WriteLn(p);
WriteLn(p^);
Dispose(p)
(c) ¿De qué forma se pueden recuperar los valores 1..N - 1 generados en el bucle for?

Trabajo Práctico Pilas

1. Un funcionario de Juzgados tramita expedientes, de forma que siempre va tomando de la bande-


ja de expedientes a tramitar el que se encuentra más arriba. Cada expediente se puede caracterizar
por un código, el asunto del que trata, la fecha de expedición y la fecha de tramitación. Un orde-
nanza es el encargado de traerle nuevos expedientes, que va dejando encima de los que ya había
en la bandeja.
a) Definir la estructura de datos más adecuada para representar la bandeja de expedientes
y la más adecuada para representar el cargamento de expedientes nuevos que trae el ordenanza.
b) Definir la operación apilar_exp, cuyo resultado es añadir a la estructura bandeja de
expedientes los datos almacenados en la estructura ordenanza.
c) Como con este sistema es fácil que algún expediente se retrase mucho, de vez en cuan-
do un juez pide que se tramite un determinado expediente urgentemente. Para ello indicar el códi-
go de dicho expediente. Definir la operación urgencia, que ermitirá buscar un determinado expe-
diente en la bandeja y eliminarlo de ella para tramitarlo.

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.

Trabajo Práctico COLAS

1. Un concesionario de coches tiene un número ilimitado m de modelos, todos en un número limi-


tado c de colores distintos. Cuando un cliente quiere comprar un coche, pide un coche de un mode-
lo y color determinados. Si el coche de ese modelo y color no está disponible en el concesionario,
se toman los datos del cliente (nombre y dirección), que verá atendida su petición cuando el coche
esté disponible. Si hay más de una petición de un coche de las mismas características, se atienden
las peticiones por orden cronológico. Se pide:
a) Definir la estructura de datos más adecuada capaz de contener las peticiones de un mo-
delo y color de coche.
b) Definir la estructura de datos global más adecuada, capaz de contener las peticiones pa-
ra todos los modelos y colores de coches del concesionario.
c) Definir una operación que, dado un cliente (nombre y dirección) que desea comprar un
coche de un modelo y color determinado, coloque sus datos como última petición de ese modelo y
color.
d) Definir una operación que, dado un modelo del que se han recibido k coches de deter-
minado color, elimine los k primeros clientes de la lista de peticiones de ese coche y los devuelva
en un vector, sabiendo que k ≤ 20.

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.

3. Un restaurante dispone de m mesas. Por cada mesa se sabe su código de identificación y su


capacidad en comensales.
Hay una cola de espera para ir ocupando las mesas, de forma que, para cada elemento, se
sabe el nombre de la persona que ha hecho la reserva y el número de comensales. Se pide:

a) Definir las estructuras de datos restaurante y espera.


b) Definir la operación Maître que, dado un identificador de mesa, devuelve el nombre de
una persona que haya hecho una reserva y que ya puede pasar al comedor junto con sus acompa-
ñantes. La estructura espera debe quedar convenientemente actualizada. Se deben hacer dos ver-
siones de la operación:
b.1) La elección se hace buscando la primera reserva que cabe en la mesa.
b.2) La elección se hace optimizando la ocupación de las mesas.

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.

Nota. Utilizar de modo adecuado el procedimiento implementado en el apartado b).

Trabajo Práctico Arboles

1. Dado el siguiente algoritmo, responde a las siguientes preguntas:

PROCEDURE Misterio (A: TArbol);


VAR
aux: TArbol;
BEGIN
IF not ArbolVacio(A) THEN
BEGIN
Misterio(SubIzq(A));
Misterio(SubDer(A));
dispose(A)
END
END;

a) ¿Qué tipo de recorrido realiza el algoritmo anterior?


b) ¿Qué hace el algoritmo?
c) Explica la ejecución del algoritmo, indicando el orden en que se efectúa, en el
caso de que se le dé como parámetro el siguiente árbol:

94

Algoritmos y estructuras de datos


2. Dado el siguiente algoritmo, responde a las siguientes preguntas:

FUNCTION Misterio (A: TArbol; m: TIPOBASE): TArbol;


VAR
aux: TArbol;
BEGIN
IF ArbolVacio(A) THEN
CrearArbol(aux)
ELSE
IF m = DatoRaiz(A) THEN
aux:= A
ELSE
BEGIN
aux:= Misterio(SubIzq(A), m);
IF ArbolVacio(aux) THEN
aux:= Misterio(SubDer(A), m)
END;
Misterio:= aux
END;

a) ¿Qué tipo de recorrido realiza el algoritmo anterior?


b) ¿Qué hace el algoritmo?
c) Se puede hacer lo mismo con otro tipo de recorrido pero, ¿qué ventajas o
inconvenientes tienen?

3. Una vez estudiado el algoritmo MISTERIO, responde y razona las siguientes


preguntas:

PROCEDURE misterio(Datos:TArbol;VAR Resultado:INTEGER);


VAR
Aux: INTEGER;
BEGIN
IF (NOT ArbolVacio(Datos)) THEN
BEGIN
misterio(SubIzq(Datos), Resultado);
misterio(SubDer(Datos), Aux);
IF Resultado < Aux THEN
Resultado := Aux;
Resultado:= Resultado+1
END
END.

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;

WHILE NOT ArbolVacio(aux) DO


BEGIN
padre:= aux;
IF v[i] < DatoRaiz(padre) THEN
aux:= SubIzq(aux)
ELSE
aux:= SubDer(aux)
END;

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,

PROCEDURE Misterio (VAR A: TArbol);


VAR
aux: TArbol;
BEGIN
IF NOT ArbolVacio(A) THEN
BEGIN
Misterio(SubDer(A));
Misterio(SubIzq(A));
aux:= SubDer(A);
A^.der:= SubIzq(A);
A^.izq:= aux
END
END;
Se pide:
a) ¿Qué recorrido se realiza?
b) ¿Qué hace el algoritmo?
c) Realizar una traza con el siguiente árbol

6. Dado el algoritmo,

PROCEDURE Misterio (A:TArbol;VAR p:INTEGER);


VAR
n, m:INTEGER;
BEGIN
IF ArbolVacio(A) THEN
p:= 0
ELSE
BEGIN
Misterio(SubIzq(A),n);
Misterio(SubDer(A),m);
p:= 1 + n + m
END
END;
Se pide:
a) ¿Qué recorrido se realiza?
b) ¿Qué hace el algoritmo?
c) Realizar una traza con el siguiente árbol

96

Algoritmos y estructuras de datos


7. Suponer que TArbol es un árbol binario con una implementación dinámica y TArbolB es un árbol
binario con una implementación estática, tal y como se han definido en los apuntes de teoría. Dado
el siguiente algoritmo:

PROCEDURE misterio(VAR A:TArbol; pos:INTEGER; est:TArbolB);


VAR
elem:TArbol;
BEGIN
IF (pos=0) THEN CrearArbol(A)
ELSE BEGIN
new(elem);
elem^.info:=est.mem[pos].info;
misterio(elem^.izq, est.mem[pos].izq, est);
misterio(elem^.der, est.mem[pos].der, est);
A:=elem
END
END;
a) Realizar una traza de la llamada misterio(A,est.raiz,est), en el caso en que el árbol est
contenga la siguiente información:
est.raiz=4
est.vacios=0 est.mem
123456
info 4 12 1 3 25 5
izq 6 3 0 1 0 0
der 5 0 0 2 0 0

Mostrar el resultado obtenido.

b) Decir qué hace el algoritmo.

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

2.-EL JUEGO DE LA VIDA

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:

1 Aquellos insectos que tienen 0, 1, 4, 5, 6, 7 u 8 vecinos mueren irremedia-


blemente.
2 Los insectos que tienen 2 o 3 vecinos sobreviven.
3 En cada celda vacía en cuya vecindad hay exactamente tres insectos, nace un nuevo in-
secto.
4 Los insectos que nacen o mueren no afectan las reglas hasta que se ha completado
un ciclo evolutivo, entendiendo por éste un ciclo en el que se ha decidido la superviven-
cia o muerte de los insectos (vivos al comenzar el ciclo) de acuerdo a las reglas mencio-
nadas.

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

3.- BIBLIOTECA INFANTIL

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

Algoritmos y estructuras de datos


cos.

Formato de la entrada (residente en el fichero de caracteres "BIBLIO.IN"): En cada línea apare-


cerán tres números (N, k y m, los tres mayores que 0 y menores que 20) separados por un único
espacio en blanco y sin blancos ni al comienzo ni al final de la línea. La última línea del fichero
contendrá siempre tres ceros.

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:

• para la primera componente, |mi-mj|<=1 o |mi-mj|=M-1, y


• para la segunda componente, |ni-nj|<=1 o |ni-nj|=N-1.

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:

• existe una casilla (m1,n1) que contiene la letra a1,


• para cada letra ai+1, 1<=i<k, existe una casilla (mi+1,ni+1) que contiene ai+1 cumpliéndose que
(mi,ni) y (mi+1,ni+1) son casillas adyacentes en el tablero, y
• no existen dos casillas (mi,ni) y (mj,nj) iguales, 1<=i, j<=k.

A la secuencia de casillas (m1,n1), ..., (mk,nk) la llamamos el camino de A en el tablero.


Así, dado el tablero 4x4 de la figura siguiente, las cadenas "SOLA", "HOLA" y "ADIOS"están conteni-
das en él, pero no sucede lo mismo con "GOZA", "HORA" ni "HALA".

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

Dado un tablero de las características anteriormente descritas y una palabra A compuesta


por letras mayúsculas, se pide calcular el camino de A. Al construir el programa, podéis supo-
ner que A está contenida en el tablero y que existe un único camino para ella.

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.

Formato de la salida (a guardar en el fichero de caracteres "CAMI.OUT"): p líneas (una para


cada letra de la palabra a tratar), siendo el contenido de la línea k igual a la casilla que aparece en
posición k dentro del camino de la palabra, de esta forma: carácter del '1' al '9' blanco carácter
del '1' al '9'

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

6.- NOTACIÓN POLACA INVERSA

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+*

Se trata de calcular una expresión dada en notación polaca inversa.

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

7.- Años Santos

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

Algoritmos y estructuras de datos


Xelmírez, y establece oficialmente que serán años santos compostelanos todos aquellos en que la
conmemoración del martirio de Santiago (el día 25 de julio) coincida en domingo.
Desde entonces, ha habido además dos años santos extraordinarios: el 1885 (concedido
por el papa León XIII mediante la bula “Deus Omnipotens”) para celebrar el redescubrimiento de
los restos del apóstol, y el 1938 para que “pudieran asistir a ganar el jubileo los contendientes de la
guerra civil que no hubiesen podido ir el año anterior”.

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

8.- EL RECTÁNGULO DE KUBRIK

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.

A: intercambia la fila superior y la fila inferior. El efecto se visualiza en la figura siguiente: de la


configuración de la izquierda se pasa a la de la derecha:
1234
8765

8765
1234

B: desplazamiento circular derecho del rectángulo:

1234
8765

4123
5876

C: rotación en sentido horario de los cuatro cuadrados del centro:

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

Algoritmos y estructuras de datos

Das könnte Ihnen auch gefallen