teoria pascal buenisima

102
Av. Juán B. Justo Nº 4287 (0223) 472-2408 Mar del Plata ABDALA - MARMOL

Upload: chetonal

Post on 26-Jun-2015

1.385 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Teoria Pascal Buenisima

Av. Juán B. Justo Nº 4287 (0223) 472-2408

Mar del Plata

ABDALA - MARMOL

Page 2: Teoria Pascal Buenisima

Algoritmos y estructuras de datos 2

Unidad Nº1 Algoritmos Contenidos:

Conceptos Iniciales. (Definición de Algoritmo, Computadora y Programa) Metodología de la Programación

4 5

Unidad Nº 2 Estructura de los Programas Contenidos

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

6 6 7 7 7

Unidad Nº 3 Entornos y Programa Pascal Contenidos.

Tipos de datos Variables y Constantes. Declaraciones Tipos de datos

o Tipos enteros o Tipos reales o Tipos carácter o Tipos Lógicos

Operaciones Básicas Asignación o igualación Trabajo Práctico Nº1

8 8 9 9

10 10 10

11 83

Unidad Nº 4 Estructuras de Control Contenidos.

Sentencias compuestas Ciclo FOR Ciclo While Ciclo REPEAT-UNTIL Bucles controlados por condición:

o Continuación del bucle o Valor centinela o Uso de banderas o interruptores

Sentencia IF...THEN...ELSE Selecciones CASE Trabajo Práctico estructuras de control

12 12 13 13 14 14 14 14 15 16 82

Unidad Nº 5 Procedimientos y Funciones Contenidos.

Definición de Procedimiento Creación de los procedimientos Uso de los procedimientos Variables locales y globales Parámetros formales y reales Mecanismos de paso de parámetros Definición de funciones Uso de las funciones Sentencias de Pascal

o Halt o INC y DEC

Funciones o Truncamiento y redondeo. o Exponenciales y logarítmicas.

17 17 17 17 18 19 20 22 22 22 22 22 22 22

Page 3: Teoria Pascal Buenisima

3

o Aritméticas. o Trigonométricas. o Generación de números aleatorios.

Comparación de cadenas. o Length o Operador + o Concat o Pos o Copy o Insert o Delete o Upcase o Str o Val

Trabajo Práctico Procedimientos y funciones

22 22 22 23 23 23 23 23 23 23 23 23 23 23

87

Unidad Nº 6 Arreglos o vectores Contenidos.

Datos enumerados y subrango Introducción a datos estructurados Arreglos

o Unidimensionales o Bi dimensionales (matrices) o Matrices tridimensionales

Vectores Paralelos. Algoritmos de búsqueda en arrays

o Búsqueda secuencial Trabajo Práctico vectores

27 27

28 28 29 30 31 89

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

Recursión, concepto La sucesión de Fibonacci Torres de Hanoi Trabajo Práctico Recursividad.

32 34 35 90

Unidad Nº8 Registros

Registros con variantes Manejo de registros: acceso a componentes y operaciones. Registros con variantes. Trabajo Práctico Registros

37 38 39 91

Unidad Nº 9 Punteros

Introducción al uso de punteros Definición y declaración de punteros El operador de dirección @ Generación y destrucción de variables dinámicas Operaciones básicas con datos apuntados Operaciones básicas con punteros El valor nil Aplicaciones no recursivas de los punteros

Asignación de objetos no simples Funciones de resultado no simple

Trabajo Práctico Punteros

41 42 42 42 43

43 45

92

Unidad Nº 10 Estructuras de datos recursivas – Listas Definición del tipo lista Inserción de elementos Eliminación de elementos Otras operaciones sobre listas

El procedimiento Eliminar K-esimo El procedimiento Insertar K-esimo

Implementación dinámica de una lista con enlace simple Implementación dinámica como lista doblemente enlazada.

47 48 50 51 52 53 53 57

Page 4: Teoria Pascal Buenisima

Algoritmos y estructuras de datos 4

Multilista

Unidad Nº 11 El tipo Pila , Cola

El tipo Pila Definición y ejemplos Implementación dinámica del tipo Pila Estructura de datos Cola Trabajo Práctico Pilas Trabajo Práctico Colas

Unidad Nº 12 Estructuras de datos no lineales. Árboles binarios

Estructuras de datos no lineales. Árboles binarios Árboles binarios. Recorrido Implementaciones del Árbol binario Recorrido de un Árbol binario Árboles binarios de búsqueda Búsqueda de un elemento Eliminación de un elemento

Unidad Nº 13 Unidad CRT

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 Reset, Assign, ReWrite, Close

Ejercicios Olimpiadas Algoritmos

59

59 60 62 93 94

67 69 70 72 73

75

79 80 81 82

83

97

Page 5: Teoria Pascal Buenisima

5

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:

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.

Entrada de datos

Algoritmo -------

Programa Resultado

Escribir el Programa Plantear el

algoritmo

Entender el

problema

Cargar el programa el

la computadora

Ejecutar el programa

Page 6: Teoria Pascal Buenisima

Algoritmos y estructuras de datos 6

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

Page 7: Teoria Pascal Buenisima

7

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-

Page 8: Teoria Pascal Buenisima

Algoritmos y estructuras de datos 8

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:

Datos Simples

Estándar

Entero

Real

Caracter

Lógico

Definidos por el usuario Subrango

Enumerado

Datos Estructurados

Simples o Estáticos

Array (vectores)

Registros

Archivos

Conjuntos

Cadenas

Compuestos o Dinámicos

Listas Lineales

Pilas

Colas

Listas enlazadas

Listas No Lineales Arboles

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

Page 9: Teoria Pascal Buenisima

9

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

Page 10: Teoria Pascal Buenisima

Algoritmos y estructuras de datos 10

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:

Page 11: Teoria Pascal Buenisima

11

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.

Page 12: Teoria Pascal Buenisima

Algoritmos y estructuras de datos 12

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

Sentencia;

Donde:

VC: es una variable de control VI: valor inicial VF: Valor final y VI<=VF

Pseudocódigo

Para VC:= VI hasta VF hacer

Sentencias

Sentencias

Fin Para

Page 13: Teoria Pascal Buenisima

13

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.

While Expresión Lógica DO

Sentencia;

Sentencia;

Pseudocódigo

Mientras Expresión Lógica Hacer

Sentencias

Sentencias

Fin Mientras

Un programa que escriba los números del 1 al 50, utilizando el ciclo WHILE se vería como sigue:

Pascal PROGRAM Ciclo_WHILE; VAR Numero : Integer;

Pseudocódigo

Inicio

Page 14: Teoria Pascal Buenisima

Algoritmos y estructuras de datos 14

BEGIN Numero := 1; WHILE Numero <= 50 DO BEGIN WriteLn (Numero); Numero := Numero +1; END; Readln; END.

Numero:=1

Mientras numero <=1 Hacer

Escribir (numero)

Numero:= Numero +1

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

Sentencia;

Sentencia;

Until Expresión Lógica

Pseudocódigo

Repetir

Sentencias

Sentencias

Hasta que Expresión Lógica

Fin Repetir Ejemplo:

Pascal PROGRAM Ciclo_RepeatUntil; VAR Numero : Integer; BEGIN Numero := 1; REPEAT WriteLn (Numero); Numero := Numero + 1; UNTIL Numero = 50; Readln; END.

Pseudocódigo

Inicio

Numero := 1

Repetir

Numero:= numero +1

Hasta que Numero =50

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

Page 15: Teoria Pascal Buenisima

15

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

Page 16: Teoria Pascal Buenisima

Algoritmos y estructuras de datos 16

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:

Pascal PROGRAM IF_THEN_ELSE; VAR Contador : Integer; BEGIN FOR contador := 1 to 50 DO BEGIN IF contador > 10 THEN WriteLn(Contador) ELSE WriteLn(‘*’); END; Readln; END.

Pseudocódigo Inicio Para Contador:=1 hasta 50 hacer Si Contador>10 entonces Escribir (contador) Sino Escribir (‘*’) 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’)

Page 17: Teoria Pascal Buenisima

17

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;

Page 18: Teoria Pascal Buenisima

Algoritmos y estructuras de datos 18

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

Page 19: Teoria Pascal Buenisima

19

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:

Page 20: Teoria Pascal Buenisima

Algoritmos y estructuras de datos 20

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); EscribirSiguiente(w); WriteLn(w)

5 6 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); IncrementarYescribir(w); WriteLn(w)

5 6 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.

Page 21: Teoria Pascal Buenisima

21

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

Page 22: Teoria Pascal Buenisima

Algoritmos y estructuras de datos 22

Program Principal Declaraciones y definiciones begin ….. llamada al procedimiento P …. end.

procedure P (parámetros); Declaraciones y definiciones begin ….. instrucciones …. 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;

Page 23: Teoria Pascal Buenisima

23

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

Page 24: Teoria Pascal Buenisima

Algoritmos y estructuras de datos 24

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 (x)

Genera un entero aleatorio entre 0 y 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 }

Page 25: Teoria Pascal Buenisima

25

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.

Page 26: Teoria Pascal Buenisima

Algoritmos y estructuras de datos 26

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

Page 27: Teoria Pascal Buenisima

27

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:

Page 28: Teoria Pascal Buenisima

Algoritmos y estructuras de datos 28

Type Dias_semana =( lunes, martes, miércoles, jueves, viernes, sabado, domingo); Modalidades = (informática, construcciones, electrónica); Var Sem1: dias_semana; M1,M2: Modalidades; Observación: Los identificadores de los valores no deben repetirse. El siguiente ejemplo es ilegal. Type Dias_semana =( lunes, martes, miércoles, jueves, viernes, sabado, domingo); Fin_semana =( sabado, domingo); sabado, domingo son valores ambiguos. Datos Subrango Un tipo de dato subrango puede definirse como un subintervalo de los tipos ordinalI

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

La variable sem1 puede tomar como contenido solo los valores lunes, mar-tes, miércoles, jueves, viernes, sabado o domingo. Las variables M1 y M2 pueden tomar como contenido los valores informáti-ca, construcciones o electrónica.

Page 29: Teoria Pascal Buenisima

29

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-gue: total:= 0; for i:= 1 to 15 do total:= total + vagon[i]

En Pseudocódigo Total:=0 Para i:= 1 hasta 15 hacer Total:= total + vagón[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

Page 30: Teoria Pascal Buenisima

Algoritmos y estructuras de datos 30

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

Page 31: Teoria Pascal Buenisima

31

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 3 B 150$ Si 2 Achaval Susana 9 A 170$ Si 3 Medina Juan 2 C 152$ No 4 Quiroga Roberto 5 J 60$ No AyN

(Vector de String) Dpto (vector de String)

Expen (vector de reales)

Pago (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-

Page 32: Teoria Pascal Buenisima

Algoritmos y estructuras de datos 32

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:

Page 33: Teoria Pascal Buenisima

33

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

Page 34: Teoria Pascal Buenisima

Algoritmos y estructuras de datos 34

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

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 FibonacciII

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.

Begin …

Llamada a subprograma

… End;

Begin …

Llamada a subprograma

End;

Begin …

Llamada a subprograma

… End;

Programa

Sub programa

Sub programa

Page 35: Teoria Pascal Buenisima

35

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 HanoiIII

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

A B C

Page 36: Teoria Pascal Buenisima

Algoritmos y estructuras de datos 36

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 mover disco 1 desde A a C mover disco 2 desde A a B mover disco 1 desde C a B mover disco 3 desde A a C mover disco 1 desde B a A mover disco 2 desde B a C mover disco 1 desde A a C

4 desde A a B mover disco 1 desde C a B mover disco 2 desde C a A mover disco 1 desde B a A mover disco 3 desde C a B mover disco 1 desde A a C mover disco 2 desde A a B 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)

Page 37: Teoria Pascal Buenisima

37

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 tNombReg = record idenCampo1: idTipo1;

idenCampo2: idTipo2; ...

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

Identificador de Registro

Tipo del Ca

Page 38: Teoria Pascal Buenisima

Algoritmos y estructuras de datos 38

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}

Page 39: Teoria Pascal Buenisima

39

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:

Page 40: Teoria Pascal Buenisima

Algoritmos y estructuras de datos 40

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)

Page 41: Teoria Pascal Buenisima

41

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

Page 42: Teoria Pascal Buenisima

Algoritmos y estructuras de datos 42

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

Page 43: Teoria Pascal Buenisima

43

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:

Page 44: Teoria Pascal Buenisima

Algoritmos y estructuras de datos 44

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.

Page 45: Teoria Pascal Buenisima

45

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,

Page 46: Teoria Pascal Buenisima

Algoritmos y estructuras de datos 46

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}

Page 47: Teoria Pascal Buenisima

47

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

Page 48: Teoria Pascal Buenisima

Algoritmos y estructuras de datos 48

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;

Page 49: Teoria Pascal Buenisima

49

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.

Page 50: Teoria Pascal Buenisima

Algoritmos y estructuras de datos 50

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.

Page 51: Teoria Pascal Buenisima

51

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;

Page 52: Teoria Pascal Buenisima

Algoritmos y estructuras de datos 52

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;

Page 53: Teoria Pascal Buenisima

53

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;

Page 54: Teoria Pascal Buenisima

Algoritmos y estructuras de datos 54

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

Page 55: Teoria Pascal Buenisima

55

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

Page 56: Teoria Pascal Buenisima

Algoritmos y estructuras de datos 56

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

Page 57: Teoria Pascal Buenisima

57

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

Page 58: Teoria Pascal Buenisima

Algoritmos y estructuras de datos 58

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.

Page 59: Teoria Pascal Buenisima

59

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

Page 60: Teoria Pascal Buenisima

Algoritmos y estructuras de datos 60

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;

Page 61: Teoria Pascal Buenisima

61

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.

Page 62: Teoria Pascal Buenisima

Algoritmos y estructuras de datos 62

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

Page 63: Teoria Pascal Buenisima

63

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:

Page 64: Teoria Pascal Buenisima

Algoritmos y estructuras de datos 64

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

Page 65: Teoria Pascal Buenisima

65

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

Page 66: Teoria Pascal Buenisima

Algoritmos y estructuras de datos 66

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

Page 67: Teoria Pascal Buenisima

67

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

Page 68: Teoria Pascal Buenisima

Algoritmos y estructuras de datos 68

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

Page 69: Teoria Pascal Buenisima

69

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)

Page 70: Teoria Pascal Buenisima

Algoritmos y estructuras de datos 70

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.

Page 71: Teoria Pascal Buenisima

71

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

Page 72: Teoria Pascal Buenisima

Algoritmos y estructuras de datos 72

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

Page 73: Teoria Pascal Buenisima

73

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.

Page 74: Teoria Pascal Buenisima

Algoritmos y estructuras de datos 74

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-

Page 75: Teoria Pascal Buenisima

75

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

Page 76: Teoria Pascal Buenisima

Algoritmos y estructuras de datos 76

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

Page 77: Teoria Pascal Buenisima

77

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:

Page 78: Teoria Pascal Buenisima

Algoritmos y estructuras de datos 78

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:

Constante Valor Color Black 0 Negro Blue 1 Azul Green 2 Verde Cyan 3 Cyan Red 4 Rojo Magenta 5 Magenta Brown 6 Marrón Light Gray 7 Gris claro

Colores para primer plano:

Constante Valor Color Dark gray 8 Gris oscuro Light Blue 9 Azul claro Light Green 10 Verde claro Light Cyan 11 Cyan claro Light Red 12 Rojo claro Light Magenta 13 Magenta claro Yellow 14 Amarillo 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

Page 79: Teoria Pascal Buenisima

79

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

Page 80: Teoria Pascal Buenisima

Algoritmos y estructuras de datos 80

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.

Page 81: Teoria Pascal Buenisima

81

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.

Page 82: Teoria Pascal Buenisima

Algoritmos y estructuras de datos 82

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;

Page 83: Teoria Pascal Buenisima

83

Var Y,x: integer; Begin y:=3;

x:=y+1; writeln(x,’ ‘,y); y:=y+1; x:=3; writeln(x,’ ‘,y);

var a , b , c : char; begin

a:= ‘a’ ; b:= ‘c’ ; c:= a ; writeln(a, b, c, ‘c’) ; readln;

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.

Page 84: Teoria Pascal Buenisima

Algoritmos y estructuras de datos 84

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.

Page 85: Teoria Pascal Buenisima

85

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.

Page 86: Teoria Pascal Buenisima

Algoritmos y estructuras de datos 86

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.

Page 87: Teoria Pascal Buenisima

87

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

Page 88: Teoria Pascal Buenisima

Algoritmos y estructuras de datos 88

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.

Page 89: Teoria Pascal Buenisima

89

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

0 3 -2 5 3 4 5 6

Page 90: Teoria Pascal Buenisima

Algoritmos y estructuras de datos 90

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;

Page 91: Teoria Pascal Buenisima

91

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)

Page 92: Teoria Pascal Buenisima

Algoritmos y estructuras de datos 92

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

Page 93: Teoria Pascal Buenisima

93

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

Page 94: Teoria Pascal Buenisima

Algoritmos y estructuras de datos 94

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:

Page 95: Teoria Pascal Buenisima

95

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;

Page 96: Teoria Pascal Buenisima

Algoritmos y estructuras de datos 96

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

Page 97: Teoria Pascal Buenisima

97

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

1 2 3 4 5 6 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 acba

abc acb bac bca cab

Page 98: Teoria Pascal Buenisima

Algoritmos y estructuras de datos 98

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-

Page 99: Teoria Pascal Buenisima

99

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 5 2 8 13 2 2 0 0 0

**4**8,**9**5,**3**1,**2**6,*10,**7 **2**3,**5,**4**1 **2*12,**4*10,**6**8,**9**5,*13**1,**7,**3*11

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". S H A Z I O L G E Z E F O H D I

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.

Page 100: Teoria Pascal Buenisima

Algoritmos y estructuras de datos 100

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 4 4 SHAZ IOLG EZEF OHDI SOLA

1 1 2 2 2 3 1 3

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

Page 101: Teoria Pascal Buenisima

101

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 1998 2004 1999 2005

1 2 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: 1 2 3 4 8 7 6 5 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: 5 1 8 3 4 7 6 2 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: 1 2 3 4 8 7 6 5 8 7 6 5 1 2 3 4 B: desplazamiento circular derecho del rectángulo: 1 2 3 4 8 7 6 5 4 1 2 3 5 8 7 6 C: rotación en sentido horario de los cuatro cuadrados del centro: 1 2 3 4 8 7 6 5

Page 102: Teoria Pascal Buenisima

Algoritmos y estructuras de datos 102

1 7 2 4 8 6 3 5 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 5 1 8 3 2 6 7 4 4

C A B C