Vous êtes sur la page 1sur 135

Algoritmos y Estructuras de Datos

1. JAVA
1.1 QU ES JAVA?

Sun describi a Java de la siguiente forma: Java: Es un lenguaje simple, orientado a objetos, distribuido, interpretado, robusto, seguro, de arquitectura neutra, portable, de alto desempeo, de hilos mltiples y dinmico. Java es un lenguaje de programacin orientado a objetos. A diferencia de C++, Java se dise desde un principio para estar orientado a objetos. La mayora de las cosas en Java son objetos; los primitivos tipos numricos, carcter y booleano son la nica excepcin. En Java, las cadenas se representan con objetos, los hilos, etc. Como los programas de Java se compilan en un formato de bytecode (cdigo de bytes) de arquitectura neutral, una aplicacin de Java se puede ejecutar en cualquier sistema, siempre y cuando dicho sistema instrumente la mquina virtual de Java. Esto resulta muy importante para las aplicaciones distribuidas en Internet u otras redes heterogneas. Para asegurar que los programas de Java sean realmente independientes, de la plataforma, hay una arquitectura nica sobre la que se compilan todos los programas en Java. Es decir, cuando se compila para una plataforma en Windows/Intel x86 se obtiene la misma salida que la compilada en un sistema Macintosh o Unix. El compilador no compila para la plataforma de origen, sino para una plataforma abstracta llamada mquina virtual de Java, o JVM-Java Virtual Machine. A Java tambin se le denomina lenguaje distribuido. Esto significa, simplemente, que proporciona un soporte de alto nivel para redes. Por ejemplo, la clase URL y las clases relacionadas en el paquete java.net hacen que la lectura de un archivo o una fuente remota sea tan fcil como leer un archivo local. Java se ha diseado para escribir software robusto y muy confiable. Una de las cosas que hace simple a Java es la carencia de punteros y aritmtica de puntero. Todos los accesos a arreglos y cadenas se verifican al tiempo de ejecucin. Las formas de los objetos de un tipo a otro tambin se verifican al momento de la ejecucin. La recoleccin automtica de basura en Java evita que la memoria tenga fugas, as como la presencia de otros defectos perniciosos relacionados con la asignacin y desasignacin de memoria.

Algoritmos y Estructuras de Datos

El manejo de excepciones es otra caracterstica de Java que contribuye a formar programas ms robustos. Una excepcin es una seal de que ha ocurrido una condicin excepcional, por ejemplo, un error de no se puede encontrar el archivo. Con esto puede simplificar la tarea del manejo y recuperacin de errores. Uno de los aspectos ms resonados de Java es que se trata de un lenguaje seguro. Proporciona varias capas de controles de seguridad que protegen contra cdigo malicioso; estas capas permiten a los usuarios ejecutar con comodidad programas desconocidos, como los applet. Java es un lenguaje de hilos mltiples; proporciona soporte para varios hilos de ejecucin que pueden manejar diferentes tareas. Un beneficio importante de los hilos mltiples es que se mejora el desempeo interactivo de las aplicaciones grficas para el usuario.

1.2

JAVA VS C++

Java se asemeja mucho a C y C++. Esta similitud, evidentemente intencionada, es la mejor herramienta para los programadores, ya que facilita en gran manera su transicin a Java. Desafortunadamente, tantas similitudes hacen que no nos paremos en algunas diferencias que son vitales. La terminologa utilizada en estos lenguajes, a veces es la misma, pero hay grandes diferencias subyacentes en su significado. C tiene tipos de datos bsicos y punteros. C++ modifica un poco este panorama y le aade los tipos referencia. Java tambin especifica sus tipos primitivos, elimina cualquier tipo de punteros y tiene tipos referencia mucho ms claros. Conocemos ya ampliamente todos los tipos bsicos de datos: datos base, integrados, primitivos e internos; que son muy semejantes en C, C++ y Java; aunque Java simplifica un poco su uso a los desarrolladores haciendo que el chequeo de tipos sea bastante ms rgido. Adems, Java aade los tipos boolean y hace imprescindible el uso de este tipo booleano en sentencias condicionales.

1.3 LA SIMPLICIDAD DE JAVA


Java ha sido diseado de modo de eliminar las complejidades de otros lenguajes como C y C++. Si bien Java posee una sintaxis similar a C, con el objeto de facilitar la migracin de C hacia a Java, Java es semnticamente muy distinto a C: Java no posee aritmtica de punteros: La aritmtica de punteros es el origen de muchos errores de programacin que no se manifiestan durante

Algoritmos y Estructuras de Datos

la depuracin y que una vez que el usuario los detecta son difciles de resolver. No se necesita hacer delete: Determinar el momento en que se debe liberar el espacio ocupado por un objeto es un problema difcil de resolver correctamente. Esto tambin es el origen a errores difciles de detectar y solucionar. No hay herencia mltiple: En C++ esta caracterstica da origen a muchas situaciones de borde en donde es difcil predecir cul ser el resultado. Por esta razn en Java se opta por herencia simple que es mucho ms simple de aprender y dominar.

1.4 JAVA ES INDEPENDIENTE DE LA PLATAFORMA


La independencia de la plataforma es la capacidad del programa de trasladarse con facilidad de un sistema computacional a otro. Esta independencia de la plataforma es una de las principales ventajas que tiene Java sobre otros lenguajes de programacin, en particular para los sistemas que necesitan trabajar en varias plataformas. A nivel de cdigo fuente, los tipos primitivos de datos Java, tiene tamaos consistentes, en todas las plataformas de desarrollo. Los fundamentos de bibliotecas de Java facilitan la escritura del cdigo, el cual puede desplazarse de plataforma a plataforma sin necesidad de volver a escribirlo. Los archivos binarios Java, tambin son independientes de la plataforma, y pueden compilarse en mltiples plataformas sin necesidad de volver a compilar la fuente, esto se logra, ya que los archivos binario Java, se encuentran en una forma llamada bytecode (conjunto de instrucciones parecidas al lenguaje de mquina, pero que no son especficas para un procesador). El ambiente de desarrollo Java tiene dos partes: un compilador y un intrprete Java. El compilador Java toma su programa Java y en lugar de generar cdigos de mquina para sus archivos fuente, genera un bytecode. Para ejecutar un programa Java debe utilizar un intrprete de bytecode el cual a su vez ejecuta su programa Java. Puede ejecutar el intrprete por si mismo o en el caso de los applets puede recurrir a los visualizadores que los ejecutan.

Algoritmos y Estructuras de Datos

Compilador Java

Interprete Java

Cdigo de Java

Pentium bytecode de Java

PowerPC

SPARC

La desventaja de utilizar bytecode se halla en la velocidad de ejecucin, puesto que los programas especficos del sistema corren directamente en el hardware en que se compilaron estos, ya que los bytecodes deben ser procesados por el intrprete Los programas en Java pueden ejecutarse en cualquiera de las siguientes plataformas, sin necesidad de hacer cambios: Windows/95 y /NT Power/Mac Unix (Solaris, Silicon Graphics, ...)

La compatibilidad es total:
A nivel de fuentes: El lenguaje es exactamente el mismo en todas las plataformas. A nivel de bibliotecas: En todas las plataformas estn presentes las mismas bibliotecas estndares. A nivel del cdigo compilado: el cdigo intermedio que genera el compilador es el mismo para todas las plataformas. Lo que cambia es el intrprete del cdigo intermedio.

Algoritmos y Estructuras de Datos

1.5 SEGURIDAD EN JAVA


La seguridad es un aspecto importante en Java, el visualizador baja el cdigo de toda la red y lo ejecuta en el anfitrin del usuario, se incluye varias capas de seguridad tales como: El propio lenguaje Java incluye restricciones cerradas de acceso a memoria muy diferentes al modelo de memoria que utiliza el lenguaje C. Estas restricciones incluyen la remocin de apuntadores aritmticos y de operadores de conversin forzada ilegales. Una rutina de verificacin de cdigos de byte en el intrprete de Java verifica que los cdigos de byte (Bytecodes) no violen ninguna construccin del lenguaje (lo que podra suceder si utiliza un compilador de Java alterado). Esta rutina de verificacin se asegura de que el cdigo no falsifique apuntadores, memoria de acceso restringido u objetos de acceso diferentes a los que corresponden a sus definiciones. Esta verificacin tambin asegura que las llamadas al mtodo incluyen el nmero correcto de argumentos del tipo adecuado, y que no hay desbordamiento de pilas. Una verificacin del nombre de la clase y de las restricciones de acceso sobre la carga. Una interfaz de sistema de seguridad que refuerza las polticas de seguridad en varios niveles. Al nivel de acceso del archivo, si un cdigo de byte intenta el acceso a un archivo para el que no tiene permiso, aparecer una caja de dilogo para permitir que el usuario contine o detenga la ejecucin. Al nivel de red, se tendrn opciones para emplear codificacin de clave pblica y otras tcnicas de encriptacin para verificar la fuente del cdigo y su integridad despus de haber pasado por la red. Esta tecnologa de encriptacin ser la clave parra transacciones financieras seguras a travs de la red. Al momento de la ejecucin, puede utilizarse la informacin sobre el origen del cdigo de byte para decidir lo que el cdigo puede hacer. El mecanismo de seguridad puede indicar si un cdigo de byte se origin o no desde el interior de una firewall (barrera de proteccin). Tambin se puede definir una poltica de seguridad que restrinja el cdigo dnde no se confa. Java siempre checa los ndices al acceder un arreglo. Java realiza chequeo de tipos durante la compilacin (al igual que C). En una asignacin entre punteros el compilador verifica que los tipos sean compatibles. Adems, Java realiza chequeo de tipos durante la ejecucin (cosa que C y C++ no hacen). Cuando un programa usa un cast para accesar un objeto como si fuese de un tipo especfico, se verifica durante la ejecucin que el objeto en cuestin sea compatible con el cast que se le aplica. Si el objeto no es compatible, entonces se levanta una excepcin que informa al programador la lnea exacta en donde est la fuente del error. 5

Algoritmos y Estructuras de Datos

Java posee un recolector de basuras que administra automticamente la memoria. Es el recolector el que determina cuando se puede liberar el espacio ocupado por un objeto. El programador no puede liberar explcitamente el espacio ocupado por un objeto.
Java no posee aritmtica de punteros, porque es una propiedad que no se necesita para programar aplicaciones. En C slo se necesita la aritmtica de punteros para programa malloc/free o para programar el ncleo del sistema operativo.

Por lo tanto Java no es un lenguaje para hacer sistemas operativos o administradores de memoria, pero s es un excelente lenguaje para programar aplicaciones.

1.6 JAVA ES FLEXIBLE


Pascal tambin es un lenguaje robusto, pero logra su robustez prohibiendo tener punteros a objetos de tipo desconocido. Lamentablemente esta prohibicin es demasiado rgida. Aunque son pocos los casos en que se necesita tener punteros a objetos de tipo desconocido, las contorsiones que estn obligados a realizar los programadores cuando necesitan estos punteros dan origen a programas ilegibles. Lisp por su parte es un lenguaje flexible y robusto. Todas las variables son punteros a objetos de cualquier tipo (un arreglo, un elemento de lista, etc.). El tipo del objeto se encuentra almacenado en el mismo objeto. Durante la ejecucin, en cada operacin se chequea que el tipo del objeto manipulado sea del tipo apropiado. Esto da flexibilidad a los programadores sin sacrificar la robustez. Lamentablemente, esto hace que los programas en Lisp sean poco legibles debido a que al estudiar su cdigo es difcil determinar cul es el tipo del objeto que referencia una variable. Java combina flexibilidad, robustez y legibilidad gracias a una mezcla de chequeo de tipos durante la compilacin y durante la ejecucin. En Java se pueden tener punteros a objetos de un tipo especfico y tambin se pueden tener punteros a objetos de cualquier tipo. Estos punteros se pueden convertir a punteros de un tipo especfico aplicando un cast, en cuyo caso se chequea en tiempo de ejecucin de que el objeto sea de un tipo compatible. El programador usa entonces punteros de tipo especfico en la mayora de los casos con el fin de ganar legibilidad y en unos pocos casos usa punteros a tipos desconocidos cuando necesita tener flexibilidad. Por lo tanto Java combina la robustez de Pascal con la flexibilidad de Lisp, sin que lo programas pierdan legibilidad en ningn caso.

Algoritmos y Estructuras de Datos

1.7 JAVA ADMINISTRA AUTOMTICAMENTE LA MEMORIA


En Java los programadores no necesitan preocuparse de liberar un trozo de memoria cuando ya no lo necesitan. Es el recolector de basuras el que determina cuando se puede liberar la memoria ocupada por un objeto. Un recolector de basuras es un gran aporte a la productividad. Se ha estudiado en casos concretos que los programadores han dedicado un 40% del tiempo de desarrollo a determinar en qu momento se puede liberar un trozo de memoria. Adems este porcentaje de tiempo aumenta a medida que aumenta la complejidad del software en desarrollo. Es relativamente sencillo liberar correctamente la memoria en un programa de 1000 lneas. Sin embargo, es difcil hacerlo en un programa de 10000 lneas. Y se puede postular que es imposible liberar correctamente la memoria en un programa de 100000 lneas. Para entender mejor esta afirmacin, supongamos que hicimos un programa de 1000 lneas hace un par de meses y ahora necesitamos hacer algunas modificaciones. Ahora hemos olvidado gran parte de los detalles de la lgica de este programa y ya no es sencillo determinar si un puntero referencia un objeto que todava existe, o si ya fue liberado. Peor an, suponga que el programa fue hecho por otra persona y evale cuan probable es cometer errores de memoria al tratar de modificar ese programa. Ahora volvamos al caso de un programa de 100000 lneas. Este tipo de programas los desarrolla un grupo de programadores que pueden tomar aos en terminarlo. Cada programador desarrolla un mdulo que eventualmente utiliza objetos de otros mdulos desarrollados por otros programadores. Quin libera la memoria de estos objetos? Cmo se ponen de acuerdo los programadores sobre cundo y quin libera un objeto compartido? Como probar el programa completo ante las infinitas condiciones de borde que pueden existir en un programa de 100000 lneas? Es inevitable que la fase de prueba dejar pasar errores en el manejo de memoria que slo sern detectados ms tarde por el usuario final. Probablemente se incorporan otros errores en la fase de mantenimiento. Se puede concluir: Todo programa de 100000 lneas que libera explcitamente la memoria tiene errores latentes. Sin un recolector de basuras no hay verdadera modularidad.

Algoritmos y Estructuras de Datos

Un recolector de basuras resuelve todos los problemas de manejo de memoria en forma trivial.

La pregunta es: Cul es el impacto de un recolector de basura en el desempeo de un programa? El sobrecosto de la recoleccin de basuras no es superior al 100%. Es decir si se tiene un programa que libera explcitamente la memoria y que toma tiempo X, el mismo programa modificado de modo que utilice un recolector de basuras para liberar la memoria tomar un tiempo no superior a 2X. Este sobrecosto no es importante si se considera el peridico incremento en la velocidad de los procesadores. El impacto que un recolector de basura en el tiempo de desarrollo y en la confiabilidad del software resultante es muchos ms importante que la prdida en eficiencia.

1.8 LA APLICACIN HELLO WORLD


Una aplicacin es un programa convencional que se invoca desde el intrprete de comandos. Este programa se carga directamente desde el disco y no de la red Internet. Ahora veremos la aplicacin ms simple que se puede escribir en Java: el clsico ``Hello World''. Crear un archivo llamado Hello1.java con: // La aplicacin Hello World! public class Hello1 { public static void main (String args[]) { System.out.println("Hello World!"); } } Compilar con: javac Hello1.java Ejecutar con: java Hello1

Observaciones: La primera lnea es un comentario. Todo lo que viene despus de la secuencia // hasta el fin de lnea es un comentario. Java tambin acepta comentarios ``a la C'': /* ... */

Algoritmos y Estructuras de Datos

Luego viene la definicin de una clase llamada Hello1: public class Hello1 { ... } En Java un programa es un conjunto de definiciones de clases que estn dispuestas en uno o ms archivos.

Dentro de la clase Hello1 se define el mtodo main: public static void main (String args[]) { ... } En una clase se definen uno o ms mtodos. Las palabras public y static son atributos del mtodo que discutiremos ms tarde. La palabra void indica que el mtodo main no retorna ningn valor. La forma (String args[]) es la definicin de los argumentos que recibe el mtodo main. En este caso se recibe un argumento. Los parntesis [] indican que el argumentos es un arreglo y la palabra String es el tipo de los elementos del arreglo. Por lo tanto main recibe como argumento un arreglo de strings que corresponden a los argumentos con que se invoca el programa. La instruccin System.out.println(...) despliega un string en la consola. Java no posee una sintaxis abreviada para desplegar strings.

Consideraciones importantes: El nombre del archivo (Hello1.java) siempre debe ser el nombre de la clase (Hello1) con la extensin ``.java''. Todas las aplicaciones deben definir el mtodo main. Al invocar el intrprete de java con java Hello1, se busca y se invoca un mtodo main que textualmente haya sido definido con: public static void main (String args[]) { ... } No cambie el nombre de este procedimiento ni omita ninguno de sus atributos. Tampoco cambie el tipo de los argumentos o el valor retornado.

Algoritmos y Estructuras de Datos

1.9 EL APPLET HELLO WORLD


Un applet es un programa que anima una porcin de una pgina Web. Se recupera a partir de la red y corre en la mquina del usuario, pero con muchas restricciones de modo que no pueda afectar la integridad del ambiente del usuario.
A continuacin veremos la versin applet del ejemplo anterior. Es decir un programa que coloca en una pgina Web el mensaje ``Hello World!''. Crear el programa fuente Hello2.java con: import java.awt.Graphics; import java.applet.Applet; public class Hello2 extends Applet { public void paint(Graphics g) { g.drawString("Hello world!", 50, 25); } } Compilar con: javac Hello2.java Crear la pgina Hello.html con el siguiente contenido: <html> <body> Este es un applet: <applet code="Hello2.class" width=150 height=25> </applet> </body> </html> Atencin: Hello.html debe estar en el mismo directorio que Hello2.java. Ver el applet con: appletviewer Hello.html El mismo applet tambin se puede ver desde un browser Web como netscape 2.x o superior.

Observaciones: Dado que un applet no se invoca desde el intrprete de comandos, no tiene sentido definir el mtodo main. El browser Web notifica al applet 10

Algoritmos y Estructuras de Datos

que debe dibujar su contenido invocando el mtodo paint. Esto ocurre cada vez que se muestra la porcin de la pgina html que contiene este applet. Por lo tanto un applet debe definir el mtodo paint (en vez de main). Las instrucciones: import java.awt.Graphics; import java.applet.Applet; indican que dentro del archivo las clases java.awt.Graphics y java.applet.Applet sern conocidas simplemente como Graphics y Applet.

Luego viene la definicin de la clase Hello2 con: public class Hello2 extends Applet { ... }

Las palabras extends indican que Hello2 es una extensin de la clase de biblioteca Applet. Esto significa que Hello2 es casi como Applet, solo que se modifica el comportamiento del mtodo paint. El mtodo paint recibe como argumento un objeto de tipo Graphics que corresponde a una clase de biblioteca. Este objeto se usa para dibujar en la porcin de pgina html asignada al applet. La instruccin g.drawString("Hello world!", 50, 25); dibuja el string ``Hello World!'' en la porcin asignada en las coordenadas (50, 25). Adems de programar el applet es necesario construir la pgina html que va a contener el applet. Por limitaciones de espacio en este curso slo veremos las etiquetas de html que permiten agregar un applet a la pgina, sin detenernos a ver en profundidad el lenguaje html.

1.10 INSTRUCCIONES Y EXPRESIONES


Una instruccin es lo ms sencillo que se puede hacer en Java. Una instruccin forma una sola operacin Java. Todas las siguientes son instrucciones Java sencillas: int i =1; import java.awt.Font; System.out.println(Esta motocicleta es + color + + make); m.engineState = true;

11

Algoritmos y Estructuras de Datos

Algunas veces, las instrucciones regresan valores. Por ejemplo, cuando sumamos dos valores o una prueba para ver si un valor es igual al otro. Este tipo de enunciados se llaman expresiones. Una instruccin puede ser escrita en un solo rengln o en mltiples renglones y el compilador Java lo entender perfectamente. Java tambin cuenta con instrucciones compuestas, o bloques de instrucciones, que pueden ser ubicadas en cualquier parte en cualquier lugar donde pondra una sola instruccin. Los enunciados de bloques estn rodeados por llaves({}).

1.11 LA DECLARACIN IMPORT (IMPORTAR)


La declaracin import hace que las clases de Java estn disponibles para la clase actual, bajo un nombre abreviado. Las clases pblicas de Java siempre estn disponibles va sus nombres totalmente calificados, siempre y cuando el archivo de la clase en cuestin se pueda encontrar. Hay dos formas de la declaracin import: import package.class; import package.*; La primera forma permite que la clase especificada en el paquete especificado se conozca por su nombre de clase simplemente. Por ejemplo: import java.awt.Graphics; import java.awt.Color;

La segunda forma de la declaracin import logra que todas las clases de un paquete estn disponibles mediante su nombre de clase. Por ejemplo:
import java.awt.*;

1.12 REGLAS DE ESCRITURA DE UN PROGRAMA


Los identificadores pueden iniciar con una letra, un guin de subrayado( _ ) o un signo de dlares ($), de ninguna manera puede empezar con un nmero. Despus del primer carcter, los nombres pueden incluir cualquier letra o nmero. Los identificadores no pueden ser una palabra clave de Java.

12

Algoritmos y Estructuras de Datos

El lenguaje Java utiliza un conjunto de caracteres Unicode. Esta es la definicin del conjunto de signos que no solo ofrece caracteres en el conjunto estndar ASCII, sino tambin varios millones de caracteres para representar la mayora de los alfabetos internacionales. Esto significa que puede utilizar caracteres acentuados y otros smbolos, as como caracteres legales en nombre de variables, siempre que cuenten con un nmero de carcter Unicode sobre 00C0. El lenguaje Java es sensible al tamao de las letras, la cual significa que las maysculas son diferentes de las minsculas. La variable x es diferente de X, rose no es Rose ni ROSE. Por convencin las variables Java tienen nombres significativos, con frecuencia formados de varias palabras combinadas. La primera palabra est en minsculas, pero las siguientes tienen su letra inicial en maysculas. Button the Button Long reallyBigNumber; leer. Los espacios en blanco pueden hacer que el cdigo sea ms fcil de

Cada instruccin en Java termina con un punto y coma, de no tenerlo, no se compilar el programa Java. Java tiene tres clases de comentarios. Uno de ellos, los delimitadores /* y */, rodean comentarios en varias lneas. Estos comentarios no pueden anidarse, no puede tener comentarios dentro de los comentarios. Tambin puede utilizar la doble diagonal (//) para una solo lnea de comentario, donde todo el texto hasta el final de la lnea se ignora. El ltimo tipo de comentario empieza con /** y termina con */. El contenido de estos comentarios especiales los emplea el sistema Javadoc, pero a excepcin de ste, se emplea de manera idntica que el primer tipo de comentario. Javadoc se emplea para generar la documentacin API del cdigo.

1.14 VARIABLES Y TIPOS DE DATOS

13

Algoritmos y Estructuras de Datos

Las variables son lugares en la memoria en donde pueden guardarse valores; tiene un nombre, un tipo y un valor. Antes de poder utilizar una variable, primero debe declararla y a partir de ah es factible asignarles valores. De hecho Java posee tres clases de valores: de instancia, de clase y locales. Las variables de instancia, se utilizan para definir atributos o el estado de un objeto en particular. Las variables de clase son similares a las variables de instancia, con la diferencia de que sus valores se aplican a todas las instancias de clase, en lugar de tener diferentes valores para el mismo objeto. Las variables locales, se declaran y utilizan dentro de la definicin de mtodo, por ejemplo, para contadores de ndice dentro de un ciclo, como variables temporales, o para guardar valores que solo necesita dentro de la definicin. Tambin pueden usarse dentro de bloques({}). Una vez que el mtodo o bloque termina su ejecucin, la definicin de variable y su mtodo dejan de existir. Aunque las tres clases de variables se declaran en forma parecida, las variables de clase y de instancia se accesan y se asignan en formas poco diferente a las variables locales

1.14.1 DECLARACIN DE VARIABLES


Una variable en Java es un identificador que representa una palabra de memoria que contiene informacin. El tipo de informacin almacenado en una variable slo puede ser del tipo con que se declar esa variable. Una variable se declara usando la misma sintaxis de C.
tipoVariable nombre;

Por ejemplo la siguiente tabla indica una declaracin, el nombre de la variable introducida y el tipo de informacin que almacena la variable:

Declaracin

identificador

tipo

14

Algoritmos y Estructuras de Datos

int i; String s; int a[]; int[] b;

i s a b

entero referencia a string referencia a arreglo de enteros referencia a arreglo de enteros

Todas las variables en el lenguaje Java deben tener un tipo de dato. El tipo de la variable determina los valores que la variable puede contener y las operaciones que se pueden realizar con ella. Existen dos categoras de datos principales en el lenguaje Java: los tipos primitivos y los tipos referenciados. Tipos Primitivos int, short, byte, long char, boolean float, double Referencias a Objetos Strings Arreglos otros objetos

Los tipos primitivos contienen un slo valor e incluyen los tipos como los enteros, coma flotante, los caracteres, etc... La tabla siguiente muestra todos los tipos primitivos soportados por el lenguaje Java, su formato, su tamao y una breve descripcin de cada uno:
Tipo byte short int long float double char Tamao/Formato 8-bit complemento a 2 16-bit complemento a 2 32-bit complemento a 2 64-bit complemento a 2 32-bit IEEE 754 64-bit IEEE 754 16-bit Caracter Descripcin Entero de un Byte Entero corto Entero Entero largo Coma flotante de precisin simple Coma flotante de precisin doble Un slo carcter Un valor booleano (verdadero o falso)

(Nmeros enteros)

(Nmeros reales) (otros tipos)


boolean true o false

Los ocho tipos de datos primitivos manejan tipos comunes para enteros, nmeros de punto flotante, caracteres y valores boolanos. Se llaman primitivos, porque estn integrados en el sistema y no son objetos en realidad, lo cual hace su uso ms eficiente.

15

Algoritmos y Estructuras de Datos

Observe que estos tipos de datos son independientes de la computadora, puede confiar que su tamao y caractersticas son consistentes en los programas Java.

Enteros
Existen cuatro tipos de enteros Java, cada uno con un rango diferente de valores, todos tienen signo. Tip o byte shor t int long Tama o 8 bits 16 bits 32 bits 64 bits Rango -128 a 127 -32, 768 a 32, 767 -2, 147, 483, 648 a 2, 147, 483, 647 -9,223,372,036,854,775,808 9,223,372,036,854,775,807

Tambin puede forzar a un entero ms pequeo a un long al agregarle una L o l a ese nmero. Los enteros tambin pueden representarse en sistema octal o hexadecimal: un cero indica que un nmero es octal (0777). Un 0x o 0X inicial, significa que se expresa en hexadecimal (0xFF, oXAF45).

Punto flotante
Los tipos de punto flotante contienen ms informacin que los tipos enteros. Las variables de punto flotante son nmeros fraccionarios. Existen dos subtipos de punto flotante : float y double. Puede forzar el nmero al tipo float al agregarle la letra f o F a ese nmero ( 2.56f). Tipo Double Float Rango -1.7x10-308 a 1.7x10308 -3.4x10-38 a 3.4x1038

Booleanos
El tipo booleano tiene dos valores: True y False (verdadero y falso).

Caracter

16

Algoritmos y Estructuras de Datos

El tipo carcter representa un carcter con base en el conjunto de caracteres de Unicode. Este tipo se define con la palabra clave char. El valor correspondiente a un tipo de carcter debe estar encerrado entre comillas sencillas (). Se representa con un cdigo de 16 bits, permitiendo 65,536 caracteres diferentes: una magnitud mayor que la del conjunto de caracteres ASCII/ANSI. Los valores pueden aparecer en forma normal como a,b,c o pueden representarse con una codificacin hexadecimal. Estos valores hexadecimales comienzan con el prefijo \u, a fin de que Java sepa que se trata de un valor hexadecimal. Por ejemplo, el retorno de carro en hexadecimal es \u000d. El tipo char, al igual que el booleano, no tiene un gemelo numrico. Sin embargo, el tipo char s puede convertirse a enteros en caso necesario. char coal; coal =c; La siguiente tabla muestra los cdigos especiales que pueden representar caracteres no imprimibles, as como los del conjunto de caracteres Unicode.

Escape
\n \t \b \r \f \/ \ \ \ddd \xdd \udddd

Significado
Lnea nueva Tabulador Retroceso Regreso de carro Alimentacin de forma Diagonal inversa Comilla sencilla Comilla doble Octal Hexadecimal Carcter Unicode

Cadenas y arreglos
Con excepcin de los tipos entero, de punto flotante, booleano y carcter, la mayora de los tipos restantes en Java son un objeto. En esta regla se incluyen las cadenas y los arreglos, los cuales pueden tener su propia clase. Las cadenas son slo una manera de representar una secuencia de caracteres. Ya que no son tipos integrados, tendr que declararlas mediante la clase String. As como a los tipos char se les da un valor entre comillas sencillas (), a las cadenas se les dan valores contenidos entre comillas dobles (). Es posible unir (concatenar) varias cadenas por medio del signo ms (+). Las cadenas no son simples arreglos de caracteres como lo son en C o C++, aunque s cuentan con las caractersticas parecidas a las de los arreglos. Puesto

17

Algoritmos y Estructuras de Datos

que los objetos de cadena en Java son reales, cuenta con mtodos que le permiten combinar, verificar, y modificar cadenas con gran facilidad. Las siguientes instrucciones Java declaran tres variables que usan los tipos int, float y long: class Class1 { public static void main (String args[ ]) { int test_score; float salary; long distancia_a_la_luna;} }

Asignacin e inicializacin de variables


Una vez que se ha declarado una variable puede asignarle un valor mediante el uso del operador de asignacin = Size =14; tooMuchCaffiene = true; int goo; goo=100; char coal; coal= b; El siguiente ejemplo asigna valores a tres variables y luego muestra el valor de cada una: class Class1 { public static void main (String args[ ]) { int age = 35; double salary = 25000.75; long Distancia_a_la_luna=238857; System.out.println("Employee age: " + age); System.out.println("Employee salary: " + salary); System.out.println("Distancia_a_la_luna: " + Distancia_a_la_luna); } }

Las palabras reservadas Java no pueden usarse para nombres de variables, a continuacin se muestra una tabla con las palabras reservadas que tienen un significado especial para el compilar:

18

Algoritmos y Estructuras de Datos

abstract Char Else generic In Outer Short throws volatile

boolean class extends goto interface package static transient while

break cons final if long private super try

byte continue finally implements native protected switch var

case default float import new public synchronized unsigned

cast do for inner null rest this virtual

catch double future instanceof operator return throw void

Los tipos referenciados se llaman as porque el valor de una variable de referencia es una referencia (un puntero) hacia el valor real. En Java tenemos los arrays, las clases y los interfaces como tipos de datos referenciados. Las variables de tipo referencia a objetos almacenan direcciones y no valores directamente. Una referencia a un objeto es la direccin de un rea en memoria destinada a representar ese objeto. El rea de memoria se solicita con el operador new.

Al asignar una variable de tipo referencia a objeto a otra variable se asigna la direccin y no el objeto referenciado por esa direccin. Esto significa que ambas variables quedan referenciando el mismo objeto.
La diferencia entre ambas asignaciones se observa en la siguiente figura:

19

Algoritmos y Estructuras de Datos

Esto tiene implicancias mayores ya que si se modifica el objeto referenciado por r, entonces tambin se modifica el objeto referenciado por s, puesto que son el mismo objeto. En Java una variable no puede almacenar directamente un objeto, como ocurre en C y C++.

Por lo tanto cuando se dice en Java que una variable es un string, lo que se quiere decir en realidad es que la variable es una referencia a un string.

1.14.2

NOMBRES DE VARIABLES

Un programa se refiere al valor de una variable por su nombre. Por convencin, en Java, los nombres de las variables empiezan con una letra minscula (los nombres de las clases empiezan con una letra mayscula). Un nombre de variable Java: 1. debe ser un identificador legal de Java comprendido en una serie de caracteres Unicode. Unicode es un sistema de codificacin que soporta texto escrito en distintos lenguajes humanos. Unicode permite la codificacin de 34,168 caracteres. Esto le permite utilizar en sus programas Java varios alfabetos como el Japons, el Griego, el Ruso o el Hebreo. Esto es importante para que los programadores pueden escribir cdigo en su lenguaje nativo. 2. no puede ser el mismo que una palabra clave o el nombre de un valor booleano (true or false) 3. no deben tener el mismo nombre que otras variables cuyas declaraciones aparezcan en el mismo mbito. La regla nmero 3 implica que podra existir el mismo nombre en otra variable que aparezca en un mbito diferente. Por convencin, los nombres de variables empiezan por un letra minscula. Si una variable est compuesta de ms de una palabra, como 'nombreDato' las palabras se ponen juntas y cada palabra despus de la primera empieza con una letra mayscula.

1.15 EXPRESIONES Y OPERADORES


Los operadores realizan algunas funciones en uno o dos operandos. Los operadores que requieren un operador se llaman operadores unarios. Por ejemplo, ++ es un operador unario que incrementa el valor su operando en uno.
20

Algoritmos y Estructuras de Datos

Los operadores que requieren dos operandos se llaman operadores binarios. El operador = es un operador binario que asigna un valor del operando derecho al operando izquierdo. Los operadores unarios en Java pueden utilizar la notacin de prefijo o de sufijo. La notacin de prefijo significa que el operador aparece antes de su operando: operador operando La notacin de sufijo significa que el operador aparece despus de su operando: operando operador Todos los operadores binarios de Java tienen la misma notacin, es decir aparecen entre los dos operandos: op1 operador op2 Adems de realizar una operacin tambin devuelve un valor. El valor y su tipo dependen del tipo del operador y del tipo de sus operandos. Por ejemplo, los operadores aritmticos (realizan las operaciones de aritmtica bsica como la suma o la resta) devuelven nmeros, el resultado tpico de las operaciones aritmticas. El tipo de datos devuelto por los operadores aritmticos depende del tipo de sus operandos: si sumas dos enteros, obtendrs un entero. Se dice que una operacin evala su resultado. Es muy til dividir los operadores Java en las siguientes categoras: aritmticos, relacionales y condicionales, lgicos y de desplazamiento, y de asignacin.

1.15.1 OPERADORES ARITMTICOS


El lenguaje Java soporta varios operadores aritmticos, incluyendo + (suma), (resta), * (multiplicacin), / (divisin), y % (mdulo), en todos los nmeros enteros y de punto flotante. Por ejemplo, se puede utilizar este cdigo Java para sumar dos nmeros: sumaEsto + Esto O este cdigo para calcular el resto de una divisin: divideEsto % porEsto

21

Algoritmos y Estructuras de Datos

La siguiente tabla contempla todas las operaciones aritmticas binarias en Java:

Operador
+ * / %

Uso
op1 + op2 op1 - op2 op1 * op2 op1 / op2

Descripcin

Suma op1 y op2 Resta op2 de op1 Multiplica op1 y op2 Divide op1 por op2 Obtiene el resto de dividir op1 por op1 % op2 op2

Nota: El lenguaje Java extiende la definicin del operador + para incluir la concatenacin de cadenas. Los operadores + y - tienen versiones unarias que seleccionan el signo del operando:

Operador
+ -

Uso
+ op - op

Descripcin
Indica un valor positivo Niega el operando

Adems, existen dos operadores de atajos aritmticos, ++ que incrementa en uno su operando, y -- que decrementa en uno el valor de su operando. El siguiente listado es un ejemplo de aritmtica sencilla: class Class1 { public static void main (String args[ ]) { short x = 6; int y = 4; float a = 12.5f; float b = 7f; System.out.println(" x es " + x + ", y es " + y); System.out.println(" x + y = " + (x + y )); System.out.println(" x - y = " + (x - y )); System.out.println(" x * y = " + (x * y )); System.out.println(" x % y = " + (x % y )); System.out.println(" a es " + a + ", b es " + b); System.out.println(" a / b = " + (a / b )); } } El resultado que obtendr del siguiente listado es:

22

Algoritmos y Estructuras de Datos

x es 6, y es 4 x + y = 10 x-y=2 x/y=1 x%y=2 a es 12.5, b es 7 a / b = 1.78571 El siguiente ejemplo, muestra el resultado de varias operaciones matemticas simples: class Class1 { public static void main (String args[ ]) { System.out.println(" 5+7 = " + (5+7)); System.out.println(" 12-7 = " + (12-7)); System.out.println("1.2345 * 2 = " + (1.2345 * 2 ) ); System.out.println(" 15 / 3 = " + (15 / 3 )); } }

1.15.2 INCREMENTOS Y DECREMENTOS


Como en C y C++, los operadores ++ y -- se utilizan para incrementar o decrementar un valor en 1, a diferencia de C y de C++, Java permite que el valor a modificar sea de punto flotante. Estos operadores se pueden fijar antes o despus; es decir, el ++ o el -- puede aparecer antes o despus del valor que incrementa o decrece. Veamos los siguientes ejemplos:

Operador
++ ++ ---

Uso
op ++

Descripcin

Incrementa op en 1; evala el valor antes de incrementar Incrementa op en 1; evala el valor despus de ++ op incrementar Decrementa op en 1; evala el valor antes de op -decrementar Decrementa op en 1; evala el valor despus de -- op decrementar

Veamos los siguientes ejemplos:

23

Algoritmos y Estructuras de Datos

Expresin Y = X++ Y = ++X Z=X++ +Y Z=X + --Y

Significado Y=X X=X+1 X=X+1 Y=X Z = X +Y X=X+1 Y = Y-1 Z=X+Y

El siguiente ejemplo ilustra el uso de los operadores de incremento prefijo y sufijo: class Class1 { public static void main (String args[ ]) { int small_count = 0; int big_count = 1000; System.out.println ("small_count is " + small_count); System.out.println ("small_count is " + small_count++); System.out.println ("El valor final de samall_count es " + small_count); System.out.println ("big_count is " +big_count); System.out.println ("++big_count is " + ++ big_count); System.out.println ("El valor final de big_count es " + big_count); } } El operador de sustraccin (--) disminuye en 1 el valor de la variable. Al igual que el operador de incremento soporta el prefijo y el sufijo: class Class1 { public static void main (String args[ ]) { int small_count = 0; int big_count = 1000; System.out.println ("small_count is " + small_count); System.out.println ("small_count is " + small_count--); System.out.println ("El valor final de samall_count es " + small_count); System.out.println ("big_count is " +big_count); System.out.println ("--big_count is " + -- big_count); System.out.println ("El valor final de big_count es " + big_count); } }

24

Algoritmos y Estructuras de Datos

1.15.3 OPERADORES RELACINALES Y LOGICOS


Los valores relacionales comparan dos valores y determinan la relacin entre ellos. Por ejemplo, != devuelve true si los dos operandos son distintos. Esta tabla contempla los operadores relacionales de Java:

Operador
> >= < <= == !=

Uso

Devuelve true si

op1 > op2 op1 es mayor que op2 op1 >= op1 es mayor o igual que op2 op2 op1 < op2 op1 es menor que op2 op1 <= op1 es menor o igual que op2 op2 op1 == op1 y op2 son iguales op2 op1 != op2 op1 y op2 son distintos

Frecuentemente los operadores relacionales se utilizan con otro juego de operadores, los operadores condicionales, para construir expresiones de decisin ms complejas. Uno de estos operadores es && que realiza la operacin Y booleana . Por ejemplo puedes utilizar dos operadores relacionales diferentes junto con && para determinar si ambas relaciones son ciertas. La siguiente lnea de cdigo utiliza esta tcnica para determinar si un ndice de un array est entre dos lmites, esto es, para determinar si el ndice es mayor que 0 o menor que NUM_ENTRIES (que se ha definido previamente como un valor constante): 0 < index && index < NUM_ENTRIES Observa que en algunas situaciones, el segundo operando de un operador relacional no ser evaluado. Consideremos esta sentencia: ((count > NUM_ENTRIES) && (System.in.read() != -1)) Si count es menor que NUM_ENTRIES, la parte izquierda del operando de && evala a false. El operador && slo devuelve true si los dos operandos son verdaderos. Por eso, en esta situacin se puede deteminar el valor de && sin evaluar el operador de la derecha. En un caso como este, Java no evala el operando de la derecha. As no se llamar a System.in.read() y no se leer un carcter de la entrada estndar.

1.15.4 OPERADORES LGICOS

25

Algoritmos y Estructuras de Datos

La siguiente tabla muestra tres operadores lgicos (condicionales):

Operador
&& || !

Uso

Devuelve true si

op1 && op2 op1 y op2 son verdaderos uno de los dos es op1 || op2 verdadero ! op op es falso

El operador & se puede utilizar como un sinnimo de && si ambos operadores son bolanos. Similarmente, | es un sinnimo de || si ambos operandos son bolanos.

1.15.5 OPERADORES DE BITS


Los operadores de desplazamiento permiten realizar una manipulacin de los bits de los datos. Esta tabla sumariza los operadores lgicos y de desplazamiento disponibles en el lenguaje Java:

Operador
>> << >>> & | ^ ~

Uso
op1 >> op2 op1 << op2

Descripcin

desplaza a la derecha op2 bits de op1 desplaza a la izquierda op2 bits de op1 desplaza a la derecha op2 bits de op1 >>> op2 op1(sin signo) op1 & op2 bitwise and op1 | op2 bitwise or op1 ^ op2 bitwise xor ~ op bitwise complemento

Los tres operadores de desplazamiento simplemente desplazan los bits del operando de la izquierda el nmero de posiciones indicadas por el operador de la derecha. Los desplazamientos ocurren en la direccin indicada por el propio operador. Por ejemplo: 13 >> 1; desplaza los bits del entero 13 una posicin a la derecha. La representacin binaria del nmero 13 es 1101. El resultado de la operacin de desplazamiento es 110 o el 6 decimal. Observe que el bit situado ms a la derecha desaparece. Un desplazamiento a la derecha de un bit es equivalente, pero ms eficiente que, dividir el operando de la izquierda por dos. Un desplazamiento a la izquierda es equivalente a multiplicar por dos. Los otros operadores realizan las funciones lgicas para cada uno de los pares de bits de cada operando. La funcin "y" (and) activa el bit resultante si los dos operandos son 1. op1 op2 resultado 0 0 0

26

Algoritmos y Estructuras de Datos

0 1 1

1 0 1

0 0 1

Supngase que se quiere evaluar los valores 12 "and" 13: 12 & 13 El resultado de esta operacin es 12. Por qu? Bien, la representacin binaria de 12 es 1100 y la de 13 es 1101. La funcin "and" activa los bits resultantes cuando los bits de los dos operandos son 1, de otra forma el resultado es 0. Entonces si colocas en lnea los dos operandos y realizas la funcin "and", puedes ver que los dos bits de mayor peso (los dos bits situados ms a la izquierda de cada nmero) son 1 as el bit resultante de cada uno es 1. Los dos bits de menor peso se evalan a 0 poque al menos uno de los dos operandos es 0: 1101 & 1100 ------1100 El operador | realiza la operacin or inclusiva y el operador ^ realiza la operacin or exclusiva. or inclusiva significa que si uno de los dos operandos es 1 el resultado es 1. op1 0 0 1 1 op2 0 1 0 1 resultado 0 1 1 1

or exclusivo significa que si los dos operandos son diferentes el resultado es 1, de otra forma el resultado es 0:

op1 0 0 1 1

op2 0 1 0 1

resultado 0 1 1 0

Y finalmente el operador complemento invierte el valor de cada uno de los bites del operando: si el bit del operando es 1 el resultado es 0 y si el bit del operando es 0 el resultado es 1.

27

Algoritmos y Estructuras de Datos

1.15.6 OPERADORES DE ASIGNACIN


Puedes utilizar el operador de asignacin =, para asignar un valor a otro. Adems del operador de asignacin bsico, Java proporciona varios operadores de asignacin que permiten realizar operaciones aritmticas, lgicas o de bits y una operacin de asignacin al mismo tiempo. Especficamente, supngase que se quiere aadir un nmero a una variable y asignar el resultado dentro de la misma variable, como esto: i = i + 2; Se puede hacer lo mismo utilizando el operador +=. i += 2; Las dos lneas de cdigo anteriores son equivalentes. La siguiente tabla lista los operadores de asignacin y sus equivalentes:

Operador
+= -= *= /= %= &= |= ^= <<= >>= >>>=

Uso
op1 += op2 op1 -= op2 op1 *= op2 op1 /= op2 op1 %= op2 op1 &= op2 op1 |= op2 op1 ^= op2 op1 <<= op2 op1 >>= op2 op1 >>>= op2

Equivale a
op1 = op1 + op2 op1 = op1 - op2 op1 = op1 * op2 op1 = op1 / op2 op1 = op1 % op2 op1 = op1 & op2 op1 = op1 | op2 op1 = op1 ^ op2 op1 = op1 << op2 op1 = op1 >> op2 op1 = op1 >>> op2

1.16 Expresiones
Las expresiones realizan el trabajo de un programa Java. Entre otras cosas, las expresiones se utilizan para calcular y asignar valores a las variables y para controlar el flujo de un programa Java. El trabajo de una expresin se divide en dos partes: realizar los clculos indicados por los elementos de la expresin y devolver algn valor.

28

Algoritmos y Estructuras de Datos

Definicin: Una expresin es una serie de variables, operadores y llamadas a mtodos (construida de acuerdo a la sintaxis del lenguaje) que evala a un valor sencillo. El tipo del dato devuelto por una expresin depende de los elementos utilizados en la expresin. La expresin count++ devuelve un entero porque ++ devuelve un valor del mismo tipo que su operando y count es un entero. Otras expresiones devuelven valores bolanos, cadenas, etc... Una expresin de llamada a un mtodo devuelve el valor del mtodo; as el tipo de dato de una expresin de llamada a un mtodo es el mismo tipo de dato que el valor de retorno del mtodo. El mtodo System.in.read() se ha declarado como un entero, por lo tanto, la expresin System.in.read() devuelve un entero. La segunda expresin contenida en la sentencia System.in.read() != -1 utiliza el operador !=. Recuerda que este operador comprueba si los dos operandos son distintos. En esta sentencia los operandos son System.in.read() y -1. System.in.read() es un operando vlido para != porque devuelve un entero. As System.in.read() != -1 compara dos enteros, el valor devuelto por System.in.read() y -1. El valor devuelto por != es true o false dependiendo de la salida de la comparacin. Como has podido ver, Java te permite construir expresiones compuestas y sentencias a partir de varias expresiones pequeas siempre que los tipos de datos requeridos por una parte de la expresin correspondan con los tipos de datos de la otra. Tambin habrs podido concluir del ejemplo anterior, el orden en que se evalan los componentes de una expresin compuesta. Por ejemplo, toma la siguiente expresin compuesta: x*y*z En este ejemplo particular, no importa el orden en que se evale la expresin porque el resultado de la multiplicacin es independiente del orden. La salida es siempre la misma sin importar el orden en que se apliquen las multiplicaciones. Sin embargo, esto no es cierto para todas las expresiones. Por ejemplo, esta expresin obtiene un resultado diferente dependiendo de si se realiza primero la suma o la divisin: x + y / 100 Puedes decirle directamente al compilador de Java cmo quieres que se evale una expresin utilizando los parntesis ( y ). Por ejemplo, para aclarar la sentencia anterior, se podra escribir: (x + y)/ 100. Si no le dices explcitamente al compilador el orden en el que quieres que se realicen las operaciones, l decide basndose en la precedencia asignada a los operadores y otros elementos que se utilizan dentro de una expresin. Los operadores con una precedencia ms alta se evalan primero. Por ejemplo. el operador divisin tiene una precedencia mayor que el operador suma, por eso, en la expresin anterior x + y / 100, el compilador evaluar primero y / 100. As x + y / 100 29

Algoritmos y Estructuras de Datos

es equivalente a: x + (y / 100) Para hacer que tu cdigo sea ms fcil de leer y de mantener deberas explicar e indicar con parntesis los operadores que se deben evaluar primero. La tabla siguiente muestra la precedencia asignada a los operadores de Java. Los operadores se han listado por orden de precedencia de mayor a menor. Los operadores con mayor precedencia se evalan antes que los operadores con un precedencia relativamente menor. Lo operadores con la misma precedencia se evalan de izquierda a derecha.

Precedencia de Operadores en Java operadores sufijo operadores unarios creacin o tipo multiplicadores suma/resta desplazamiento relacionales igualdad bitwise AND bitwise exclusive OR bitwise inclusive OR AND lgico OR lgico condicional asignacin [] . (params) expr++ expr-++expr --expr +expr -expr ~ ! new (type)expr */% +<< >> >>> < > <= >= instanceof == != & ^ | && || ?: = += -= *= /= %= ^= &= |= <<= >>= >>>=

1.17 SENTENCIAS DE CONTROL DE FLUJO

30

Algoritmos y Estructuras de Datos

Las sentencias de control de flujo determinan el orden en que se ejecutarn las otras sentencias dentro del programa. El lenguaje Java soporta varias sentencias de control de flujo, incluyendo:
Sentencias condicionales bucles excepciones miscelaneas palabras clave if-else, switch-case for, while, do-while try-catch-finally, throw break, continue, label:, return

1.17.1 CONDICIONAL if
La condicional if permite ejecutar diferentes partes del cdigo con base en una simple prueba, contiene la palabra clave if, seguida de una prueba booleana y de un enunciado a ejecutar si la prueba es verdadera: La diferencia entre los condicionales en Java y C o C++ es que la prueba debe regresar un valor booleano (falso o verdadero). El formato de la instruccin if es la siguiente:

If (condicin_es_verdadera) sentencia; El siguiente ejemplo usa if para comparar el valor almacenado en la variable TestScore con 90. Si TestScore es mayor o igual a 90, se muestra un mensaje de felicitacin indicando que obtuvo una A, de otra manera, si el valor es menor de 90 el programa termina. class Class1 { public static void main (String args[ ]) { int TestScore = 95; if (TestScore >= 90) System.out.println ("Felicidades obtuviste una A"); } } Para lograr que se ejecute el conjunto de instrucciones en el caso de la condicin falsa, debe utilizarse else. El formato de la instruccin else es como sigue: 31

Algoritmos y Estructuras de Datos

If (condicin_es_verdadera) Sentencia; else Sentencia; class Class1 { public static void main (String args[ ]) { int TestScore = 95; if (TestScore >= 90){ System.out.println ("Felicidades obtuviste una A"); System.out.println ("Tu puntuacin es" + TestScore); } else { System.out.println ("Deberas estudiar mas"); System.out.println ("Perdiste de tu puntuacin" + (TestScore - 10) ); } } } Cuando se lee informacin del teclado, Java prueba si hay problemas durante la entrada (por ejemplo, no hay mas datos a introducir). Si ocurre un problema Java genera una excepcin, la cual es un caso excepcional durante la ejecucin de un programa. Para ejecutar un programa que reciba datos del teclado, puede hacerlo indicndole al compilador que est consiente de los problemas que pueden ocurrir, esto se lograr incluyendo en el programa las palabras clave throws IOException. El siguiente programa el usuario va a teclear calificaciones de letra que se introducen en el programa para calcular el promedio en las calificaciones de un grupo: import java.io.*; class Class1 { public static void main (String args[ ] ) throws IOException { int counter, grade, total, average; // fase de inicializacin total = 0; counter = 1; //fase de procesamiento while (counter <= 10 ) {

32

Algoritmos y Estructuras de Datos

System.out.print ("Teclee calificacin de letra: " ); System.out.flush ( ); grade = System.in.read ( ); if (grade == 'A' ) total = total + 4; else if (grade == 'B' ) total = total + 3; else if (grade == 'C' ) total = total + 2; else if (grade == 'D' ) total = total + 1; else if (grade == 'F' ) total = total + 0; System.in.skip ( 2 ); //Saltar el carcter de nueva lnea counter = counter + 1; } //fase de terminacin average = total / 10; //divisi entera System.out.println ("El promedio del grupo es " + average); } } El operador condicional Una alternativa para utilizar las palabras claves if y else en un enunciado condicional es usar el operador condicional tambin conocido como el operador ternario. El operador condicional es ms til para condicionales cortos o sencillos, y tiene esta apariencia: test ? trueresult : falseresult El test es una expresin que regresa true o false, al igual que en la prueba del enunciado if. En el siguiente ejemplo, este condicional prueba los valores de x y y, regresa al ms pequeo de los dos y asigna ese valor a la variable smaller: int smaller = x < y ? x : y;

1.17.2 CONDICIONALES switch


Este condicional nos sirve para probar alguna variable contra algn valor, y si no coincide con ese valor, probarla con otro y as sucesivamente.

33

Algoritmos y Estructuras de Datos

switch (test) { case valueOne: resultOne; break; case valueTwo: resultTwo; break; case valueThree: resultThree; break; default: defaultresult; } En el siguiente ejemplo, se usa la instruccin switch para mostrar un mensaje, con base en la nota actual del estudiante: class Class1 { public static void main (String args[ ] ) { char grade = 'B'; switch (grade){ case 'A' :System.out.print("Felicidades obtuviste una A"); break; case 'B' :System.out.print("Bien, obtuviste una B"); break; case 'C' :System.out.print("Suficiente obtuviste una C"); break; case 'D' :System.out.print("No hay excusas, estudia ms "); break; } } } La desventaja de este tipo de pruebas, es que no pueden trabajar con valores de tipos deferentes de int, esto limita a trabajar con if anidado para la utilizacin de valores grandes como los que maneja (long y float). Tambin puede utilizar este condicional cuando quiera que ms de una opcin ejecuten la misma lnea de cdigo, esto lo podr hacer al omitir la sentencia break, lo que le permitir desplazarse hasta que encuentre el condicional de paro. switch (x){ case2: case4: 34

Algoritmos y Estructuras de Datos

case6: case8: system.out.println(x es un nmero par.); break; default: system.out.println(x es cualquier nmero.); }

1.17.3 CICLOS for


Este ciclo repite una declaracin o un bloque de enunciados un nmero de veces hasta que una condicin se cumple. Estos ciclos con frecuencia se utilizan para una iteracin sencilla en donde usted repite un bloque de enunciados un cierto nmero de veces y despus se detiene; aunque tambin puede usarlos para cualquier clase de ciclos. El ciclo for en Java tiene esta apariencia:

for (initialization; test; increment){ staments }

El inicio del ciclo for tiene tres partes: 1. initialization es una expresin que inicializa el principio del ciclo. Si tiene un ndice, esta expresin puede declararla e inicializarla. Las variables que se declararn dentro del ciclo; dejan de existir despus de acabar la ejecucin del ciclo. 2. test es la prueba que ocurre despus de cada vuelta del ciclo. La prueba puede ser una expresin booleana o una funcin que regresa un valor booleano. Si la prueba es verdadera el ciclo se ejecutar, de lo contrario el ciclo detiene su ejecucin. 3. increment es una expresin o llamada de funcin. Por lo comn el incremento se utiliza para cambiar el valor del ndice del ciclo a fin de acercar el estado del ciclo a false y se complete. La parte del enunciado del ciclo for son los enunciados que se ejecutan cada vez que se itera el ciclo, al igual que con if puede incluir un solo enunciado o un bloque. Cualquiera de las partes de un ciclo for pueden ser enunciados vacos, es decir, puede incluir un solo punto y coma sin ninguna expresin o declaracin, y esa parte del ciclo se ignorar. Es importante recordar que no debe de colocar punto y coma despus de la primera lnea del ciclo for:

35

Algoritmos y Estructuras de Datos

for (i = 4001 ; i<=10;i++); system.out.println(Hola); En este caso como el primer punto y coma termina el ciclo con enunciado vaco, el ciclo no hace nada en general, la forma correcta para la ejecucin de este ciclo sera: for (i = 4001 ; i<=10;i++) system.out.println(Hola); Ejemplos: En el siguiente ejemplo el ciclo for muestra los nmeros desde el 1 hasta el valor contenido en la variable EndCount: public class Class1 { public static void main (String[] args) { int count ; int end_count = 10; for (count = 1; count <=end_count ; count++) System.out.print("" + count); } } El programa siguiente efecta repeticiones de 1 hasta 10, mostrando y sumando cada nmero a un gran total: public class Class1 { public static void main (String[] args) { int count ; int total = 0; int end_count = 10; for (count = 1; count <=end_count ; count++) { System.out.println("Sumando " + count + "hasta " + total); total = total + count ; } System.out.println("La suma total es : " + total); } } El ciclo for no tiene la limitacin de incrementar solo en 1 la variable. El siguiente programa muestra cada quinto nmero entre uno y cincuenta:

36

Algoritmos y Estructuras de Datos

public class Class1 { public static void main (String[] args) { int count, y ; for (count = 0, y = 15; count <=50 ; count += 5, y += 15) { System.out.println( " count: " + count); } } } Dentro de un ciclo for no es obligatorio que el conteo sea ascendente, el siguiente programa usa el ciclo for para mostrar en forma descendente los nmeros del 1 al 10: public class Class1 { public static void main (String[] args) { int count, y ; for (count =1 0, y = 15; count <=50 ; count --, y += 15) System.out.println( " count: " + count); } } Los ciclos for no estn restringidos a usar valores de tipo int en las variables de control de ciclos: public class Class1{ public static void main (String[] args) { int x ; char letter; float value; for (letter = 'A', x = 5;letter <= 'Z' ;letter ++) System.out.println(""+ letter ); for (value = 0 , x = 5; value <1.0; value += 0.1, x +=20) System.out.println(""+ value); } }

1.17.4 CICLOS while y do


Al igual que los ciclos for, le permiten a un cdigo de bloque Java ejecutarse de manera repetida hasta encontrar una condicin especfica. Utilizar uno de estos tres ciclos solo es cuestin de estilo de programacin Los ciclos while y do son exactamente los mismos que en C y C++, a excepcin de que su condicin de prueba debe ser un booleano.

37

Algoritmos y Estructuras de Datos

1.17.4.1

CICLOS while

Se utilizan para repetir un enunciado o bloque de enunciados, siempre que una condicin en particular sea verdadera. Los ciclos while tienen esta apariencia: while (condition) { bodyOfLoop } La condition es una expresin booleana. Si regresa true, el ciclo while ejecutar los enunciados dentro del bodyOfLoop, y despus prueba la condicin de nuevo, repitindola hasta que sea falsa. Si la condicin es en un principio falsa la primera vez que se prueba, el cuerpo del ciclo while nunca se ejecutar. Si necesita que el ciclo por lo menos se ejecute una vez, tiene dos opciones a elegir: 1. Duplicar el cuerpo del ciclo fuera del ciclo while. 2. Utilizar un ciclo do. El ciclo do se considera la mejor opcin en ambas.

1.17.4.2

CICLOS do... while

El ciclo do es como while, excepto que do ejecuta un enunciado o bloque hasta que la condicin es false. La principal diferencia es que los ciclos while prueban la condicin antes de realizar el ciclo, lo cual hace posible que el cuerpo del ciclo nunca se ejecute si la condicin es falsa la primera vez que se prueba. As los ciclos do ejecutan el cuerpo del ciclo por lo menos una vez antes de probar la condicin. Los ciclos do se ven as: do{ bodyOfLoop } while (condition); El siguiente programa muestra los resultados obtenidos de un examen: import java.io.*; public class Class1{ public static void main (String args [ ] ) throws IOException { int passes = 0, failures = 0, student = 1, result; while (student <= 10 ){

38

Algoritmos y Estructuras de Datos

System .out.print( "Teclee resultado (1= aprob, 2= reprob ): " ); System.out.flush ( ); result = System.in.read ( ); if (result =='1' ) passes = passes +1; else failures = failures + 1; student = student + 1; System.in.skip (2); } System.out.println ("Aprobados " + passes ); System.out.println ("Reporbados " + failures ); if ( passes > 8 ) System.out.println ("Aumentar la colegiatutra " ); } }

Programa que calcula el promedio de un grupo. import java.io.*; public class Average{ public static void main (String args[ ] ) throws IOException { double average; int counter, grade, total; // fase de inicializacin total = 0; counter = 0; //fase de procesamiento System.out.print (Teclee calificacin de letra, Z para terminar: ); System.out.flush ( ); grade = System.in.read ( ); while (grade != 10 ) { 39

Algoritmos y Estructuras de Datos

if (grade == A ) total = total + 4; else if (grade == B ) total = total + 3; else if (grade == C ) total = total + 2; else if (grade == D ) total = total + 1; else if (grade == F ) total = total + 0; System.in.skip ( 1 ); //Saltar el carcter de nueva lnea counter = counter + 1; System.out.print(Teclee calificacin de letra, Z para terminar: ); System.out.flush( ); grade = System.in.read ( ); }

//fase de terminacin if (counter != 0 ) { average = (double) total / counter; System.out.println (El promedio del grupo es + average); } else System.out.println( No se introducieron calificaciones ); } }

1.17.4.3

COMO SALIR DE LOS CICLOS

En todos los ciclos (for, while y do), stos se terminan cuando la condicin que prueba se cumple. Para salir de manera anticipada del ciclo, puede utilizar las palabras clave break y continue. El uso de continue es similar al de break, a excepcin de que en lugar de detener por completo la ejecucin del ciclo, este inicia otra vez en la siguiente iteracin. Para los ciclos do y while, esto significa que la ejecucin del bloque se inicia de nuevo. Para los ciclos for la expresin de incremento se evala y despus el bloque se ejecuta .continue es til cuando se desea tener elementos especiales de condicin dentro de un ciclo.

40

Algoritmos y Estructuras de Datos

Ejemplo que muestra el uso de la instruccin break: import java.io.*; public class Class1{ public static void main (String args[ ] ) throws IOException { int count, xpos = 25; for (count = 1; count <= 10; count++) { if (count ==5) break; // Romper el ciclo slo si count ==5 System.out.println(Integer.toString (count )); } System.out.print ("Me sal del ciclo con count = " + count); } }

Ejemplo que muestra el uso de la instruccin continue: import java.io.*; public class Class1{ public static void main (String args[ ] ) throws IOException { int xPos = 25; for ( int count = 1; count <= 10; count++ ) { if ( count ==5 ) continue; //saltarse el resto del cdigo slo si count==5 System.out.println(Integer.toString ( count )); } System.out.println("Us continue para no I mprimir 5"); } }

41

Algoritmos y Estructuras de Datos

1.17.5 EXCEPCIONES
Java implementa excepciones para facilitar la construccin de cdigo robusto. Cuando ocurre un error en un programa, el cdigo que encuentra el error lanza una excepcin, que se puede capturar y recuperarse de ella. Java proporciona muchas excepciones predefinidas. try-catch-throw try { // Normalmente este cdigo corre desde arriba del bloque hasta // el final sin problemas. Pero algunas veces puede ocasionar // excepciones o salir del bloque con una sentencia break, // continue, o return. sentencias; } catch( AlgunaException e1 ) { // El manejo de un objeto de excepcion el de tipo AlgunaExcepcion // o de una subclase de ese tipo. sentencias; catch( OtraException e2 ) { // El manejo de un objeto de excepcin e2 de tipo OtraExcepcion // o de una subclase de ese tipo. }

try (tratar) La clusula try simplemente establece un bloque de cdigo que habr de manejar todas las excepciones y salidas anormales(va break, continue o propagacin de excepcin). La clsula try, por s misma, no hace nada interesante. catch (atrapar) Un bloque try puede ir seguido de cero o ms clusulas catch, las cuales especifican el cdigo que manejar los distintos tipos de excepciones. Las clusulas catch tienen una sintaxis inusual: cada una se declara con un argumento, parecido a un argumento de mtodo. Este argumento debe ser del tipo Throwable o una subclase. Cuando ocurre una excepcin, se invoca la primera clusula catch que tenga un argumento del tipo adecuado. El tipo de argumento debe concordar con el tipo de objeto de excepcin, o ser una superclase de la excepcin. Este argumento catch es vlido slo dentro del bloque catch, y hacer referencia al objeto de excepcin real que fue lanzado.

42

Algoritmos y Estructuras de Datos

1.17.6 CONTROL GENERAL DEL FLUJO


break [etiqueta] continue [etiqueta] return expr; etiqueta: sentencia; En caso de que nos encontremos con bucles anidados, se permite el uso de etiquetas para poder salirse de ellos, por ejemplo: uno: for( ) { dos: for( ) { continue; continue uno; break uno; } }

// seguira en el bucle interno // seguira en el bucle principal // se saldra del bucle principal

Si se declara una funcin para que devuelva un entero, es imprescindible que se coloque un return final para salir de esa funcin, independientemente de que haya otros en medio del cdigo que tambin provoquen la salida de la funcin. int func() { if ( a == 0 ) return 1; return 0; // es imprescindible porque se retorna un entero }

1.18 ARREGLOS Y CADENAS


Al igual que otros lenguajes de programacin, Java permite juntar y manejar mltiples valores a travs de un objeto array (matriz). Tambin se pueden manejar datos compuestos de mltiples caracteres utilizando el objeto String (cadena). 1.18.1 ARREGLOS (ARRAYS) Esta seccin te ensear todo lo que necesitas para crear y utilizar arrays en tus programas Java. Como otras variables, antes de poder utilizar un array primero se debe declarar. De nuevo, al igual que otras variables, la declaracin de un array tiene dos componentes primarios: el tipo del array y su nombre. Un tipo de array incluye el tipo de dato de los elementos que va contener el array. Por ejemplo, el tipo de dato para un array que slo va a contener elementos enteros es un array de

43

Algoritmos y Estructuras de Datos

enteros. No puede existir un array de tipo de datos genrico en el que el tipo de sus elementos est indefinido cuando se declara el array. Aqu tienes la declaracin de un array de enteros: int[] arrayDeEnteros; La parte int[] de la declaracin indica que arrayDeEnteros es un array de enteros. La declaracin no asigna ninguna memoria para contener los elementos del array. Si se intenta asignar un valor o acceder a cualquier elemento de arrayDeEnteros antes de haber asignado la memoria para l, el compilador dar un error como este y no compilar el programa: testing.java:64: Variable arraydeenteros may not have been initialized. Para asignar memoria a los elementos de un array, primero se debe ejemplarizar el array. Se puede hacer esto utilizando el operador new de Java. (Realmente, los pasos que se deben seguir para crear un array son similares a los se deben seguir para crear un objeto de una clase: declaracin, ejemplarizacin e inicializacin. La siguiente sentencia asigna la suficiente arrayDeEnteros pueda contener diez enteros. int[] arraydeenteros = new int[10] En general, cuando se crea un array, se utiliza el operador new, ms el tipo de dato de los elementos del array, ms el nmero de elementos deseados encerrado entre cochetes cuadrados ('[' y ']'). TipodeElemento[] NombredeArray = new TipodeElementos[tamanoArray] Ahora que se ha asignado memoria para un array ya se pueden asignar valores a los elemetos y recuperar esos valores: for (int j = 0; j < arrayDeEnteros.length; j ++) { arrayDeEnteros[j] = j; System.out.println("[j] = " + arrayDeEnteros[j]); } Como se puede ver en el ejemplo anterior, para referirse a un elemento del array, se aade corchetes cuadrados al nombre del array. Entre los corchetes caudrados se indica (bien con una variable o con una expresin) el ndice del elemento al que se quiere acceder. Observa que en Java, el ndice del array empieza en 0 y termina en la longitud del array menos uno. Hay otro elemento interesante en el pequeo ejemplo anterior. El bucle for itera sobre cada elemento de arrayDeEnteros asignndole valores e imprimiendo esos valores. Observa el uso de arrayDeEnteros.length para obtener el tamao real del array. length es una propiedad proporcionada para todos los arrays de Java. Los arrays pueden contener cualquier tipo de dato legal en Java incluyendo los tipos de referencia como son los objetos u otros array. Por ejemplo, el siguiente ejemplo declara un array que puede contener diez objetos String. String[] arrayDeStrings = new String[10]; 44 memoria para que

Algoritmos y Estructuras de Datos

Los elementos en este array son del tipo referencia, esto es, cada elemento contiene una referencia a un objeto String. En este punto, se ha asignado suficiente memoria para contener las referencias a los Strings, pero no se ha asignado memoria para los propios strings. Si se intenta acceder a uno de los elementos de arraydeStrings obtendr una excepcin 'NullPointerException' porque el array est vacio y no contiene ni cadenas ni objetos String. Se debe asignar memoria de forma separada para los objetos String: for (int i = 0; i < arraydeStrings.length; i ++) { arraydeStrings[i] = new String("Hello " + i); }

1.18.2 Strings
Una secuencia de datos del tipo carcter se llama un string (cadena) y en el entorno Java est implementada por la clase String (un miembro del paquete java.lang). String[] args; Este cdigo declara explcitamente un array, llamado args, que contiene objetos del tipo String. Los corchetes vacios indican que la longitud del array no se conoce en el momento de la compilacin, porque el array se pasa en el momento de la ejecucin. El segundo uso de String es el uso de cadenas literales (una cadena de caracteres entre comillas " y "): "Hola mundo!" El compilador asigna implicitamente espacio para un objeto String cuando encuentra una cadena literal. Los objetos String son inmutables - es decir, no se pueden modificar una vez que han sido creados. El paquete java.lang proporciona una clase diferente, StringBuffer, que se podr utilizar para crear y manipular caracteres al vuelo. Concatenacin de Cadenas Java permite concatenar cadenas facilmente utilizando el operador +. El siguiente fragmento de cdigo concatena tres cadenas para producir su salida: "La entrada tiene " + contador + " caracteres." Dos de las cadenas concatenadas son cadenas literales: "La entrada tiene " y " caracteres.". La tercera cadena - la del cadena y luego se concatena con las otras.

45

Algoritmos y Estructuras de Datos

2. GENERALIDADES
2.1 Caractersticas de las Estructuras de Datos (ED). Se sabe que los programas actan sobre la informacin para lograr el propsito de acin resolver un problema. Tal informacin se dispondr de una manera particular, organizada en forma que se faciliten las operaciones que conforman el algoritmo para lograr la solucin. Hablar de organizacin conlleva a distinguir aquellos elementos que nos permiten describir las relaciones presentes, en este caso, alrededor de la informacin. El trmino Estructura de Datos refiere a dos partes de la Organizacin de la Informacin.
INFORMACIN

PROGRAMA

Solucin de un problema

Organizacin Lgica: Involucra todo aquello que tenga que ver con las partes de cada elemento, tipo de los elementos, referencia a alguno o algunos elementos, cantidad de los elementos que contiene la estructura, relaciones entre los elementos, etc. Organizacin Fsica: Se refiere a todo aquello que tenga que ver con la ubicacin de la informacin en la memoria y la forma de almacenarla de acuerdo a sus dominios, es decir, traducir los aspectos de la organizacin lgica en direcciones de memoria y tamaos, reflejando las relaciones de los datos dentro de la memoria de la mquina.
Estructura de Datos

Se caracteriza parcialmente por sus dos tipos de organizacin

Las operaciones que se pueden realizar sobre los elementos de la estructura

Existen tres operaciones bsicas para manejar todo tipo de ED, a saber:
Eliminar un elemento Aadir un elemento Buscar un elemento

De lo anterior se tiene que, al cambiar la organizacin de la informacin, entonces debemos cambiar los algoritmos que la manipulan, de aqu que se tenga lo siguiente:
46

Algoritmos y Estructuras de Datos

E.D.

Vnculo

Algoritmo

Plantea un concepto ms amplio que es el tipo Ejemplo: Sea un arreglo de 50 elementos de tipo entero en donde se aadan y eliminen elementos.

A[1] A[2]

A[3]

.
A[50] Arreglo de un ndice; Inicio: 1, Fin: 50; Tipo de elementos: Entero;

Organizacin Lgica

Organizacin Fsica

Almacenamiento: Secuencial Direccin Inicial: dir(A) Tamao del elemento: 2 bytes; Nmero de elementos: 50; Analicemos la operacin de eliminacin de un elemento en dos formas. a) b) por desplazamiento. por marca.

47

Algoritmos y Estructuras de Datos

elimina1(x) { busca1 (x,pos); SI pos = 0 ENTONCES ESCRIBE (no se encuentra,x) SINO { PARA k = pos HASTA pfin-1 HACER A[k] = A[k+1] pfin = pfin-1 } } elimina2(x) { busca2 (x, pos); SI pos = 0 ENTOCES ESCRIBE (no se encuentra,x) SINO A[pos].marca = m; } Podemos observar en los anteriores algoritmos que el segundo es ms rpido, mientras que el primero se limita a ocupar el espacio mnimo. En conclusin, viendo a las ED como entidades que integran sus operaciones, siempre se encontraran dos parmetros antagnicos, a saber: cantidad de memoria que consume la estructura contra tiempo de realizacin de una operacin.

2.2 Tipos de Estructuras de Datos y sus Dominios A partir de las diferentes formas que existen para organizar la informacin tenemos que en cuanto a la Organizacin Lgica los diversos lenguajes de programacin proporcionan los elementos bsicos de informacin y constructores para definir ED.
Tomando como base los tipos de datos que la mayora de los lenguajes proporcionan, tenemos que: los tipos escalares son: Enteros, Reales, Booleanos, Carcter (adems del tipo enumerado) y tambin se proporcionan constructores para formar estructuras tales como: ARREGLOS: que permiten definir estructuras con n elementos, todos del mismo tipo.

48

Algoritmos y Estructuras de Datos

REGISTROS * : que permiten definir estructuras con n elementos, llamados campos, que pueden ser de cualquier tipo y, CONJUNTOS * : que permiten definir estructuras con elementos de diferentes tipos, tales estructuras pueden ser manipuladas a travs de las operaciones de conjuntos.

Tales constructores se caracterizan en funcin de los dominios de los datos que puedan contener, as para los tipos bsicos tenemos: Enteros (Z) Reales ( R ) Booelanos (B) Carcter ( C ) De lo anterior, tenemos que para un arreglo de n enteros le corresponde el dominio n Z , es decir elementos de la forma (z1, z2, ... , zn ) zi Z El REGISTRO proporciona heterogeneidad en este producto cartesiano; por ejemplo: Sea x un REGISTRO con los campos y de tipo ENTERO z de tipo REAL tiene como dominio Dom (x) = Z X R, cuyos elementos son de la forma (a,b) a Z y bR. En general, tenemos que por ejemplo: Sea k un REGISTRO con los campos l de tipo booleano m de tipo x

Dom(k) = B X Dom(x)
Es decir, el producto cartesiano de cada campo definido. En el caso de los registros variantes tendremos en los dominios la unin de los dominios de cada campo o campos que pueda formar el registro. Entonces para Sea u un REGISTRO, tal que, en CASO de que c (de tipo booleano) sea igual a Cierto : el registro tendr el campo a de tipo carcter Falso : el registro tendr el campo b de tipo entero Su dominio resulta: Dom (u) = {(true,a) a C} U {(false, b) b Z}
*

Cabe sealar que aunque en lenguajes como C, C++ y JAVA no existen especficamente los REGISTROS y los CONJUNTOS y en otros lenguajes si, se mencionan aqu para establecer una diferencia entre algunas de las estructuras que sern consideradas en este tema. Para el lector no deber implicar dificultad alguna crear estructuras, con tales caractersticas, con el constructor struct de dichos lenguajes.

49

Algoritmos y Estructuras de Datos

Para poder generalizar esto ltimo, se deben considerar los elementos de referencia (apuntadores), para ello consideremos que en el siguiente ejemplo el operador ^ define tales elementos:
Sea p un REGISTRO con los campos xc, yc de tipo Real Sea l-p un REGISTRO con los campos punto de tipo p sp de tipo ^l-p Sea type una cadena que puede tomar los valores recta o crculo o polgono Sea f un REGISTRO con los campos c de tipo color g de tipo Real en CASO de que figura (de tipo type) sea igual a recta: el registro tendr los campos p1, p2 de tipo p crculo: el registro tendr los campos p1, p2 de tipo p y ngulo de tipo Real polgono: el registro tendr el campo lista-p de tipo l-p Dom (f) = Dom (color) X R X ({recta} X Dom (p)2 U {crculo} X Dom (p)2 X R U {polgono} X Dom (l-p)) Dom (l-p) define un dominio recursivo: Dom (l-p) = Dom (p) x dom (^l-p) Los elementos de Dom (^l-p) son de la forma Dom(^l-p) = {nil} U [Dom (l-p)] Los smbolos [ y ] se emplean para hacer referencia a un elemento del dominio que encierran. Tal referencia a nivel de conjuntos es irrelevante ya que los conjuntos [Dom (w)] y Dom(w) son isomorfos, se tiene que Dom (^l-p) es isomorfo a Dom(p) * Dom (l-p). Este tipo de dominios consideran como elementos secuencias finitas de la forma: (p, [ ]), (p, [p, []]),,(p, [p,,[p, []] ) donde [] significa nil. En resumen, todo lo anterior tiene que ver con la Organizacin Lgica de las Estructuras de Datos. Veamos ahora una caracterstica general de la Organizacin Fsica.

50

Algoritmos y Estructuras de Datos

Como hemos visto, la organizacin fsica tiene que ver con el lugar y la forma dentro de la memoria donde se almacena la informacin. Tanto el lugar como la forma son representados por el espacio el cual puede ser fijo o variable. De lo anterior, se tiene que las E.D. se clasifican en: a) Estticas (Espacio Fijo) b) Dinmicas (Espacio Variante) Las estructuras de datos del tipo a) estn orientados a mantener informacin, por lo general, para consulta. Se puede observar que es en las estructuras de datos del tipo b) en donde es posible variar el tamao de los elementos del dominio de tales estructuras. Esto, nos ofrece una gua muy general de seleccin de las estructuras de datos para las aplicaciones, ya que es necesario analizar los algoritmos que actan en ellas. Catlogo de algunas estructuras de datos conocidas. Dinmicas: D A Lista ligada simple. D A Circular sublistas Digrficas rboles

P/A A

re iz

co D iz

arr de arr

iz

Ortogonal

Ligada doble D aba der Anular

Estticas:
- Arreglo Unidimensional Multidimensional

- Pila Multipila - Cola Cola doble

51

Algoritmos y Estructuras de Datos

2.3 Almacenamiento Secuencial Las estructuras de datos con este tipo de almacenamiento son los ms fciles de manejar debido a que la memoria de la computadora tiene una estructura secuencial, algunas estructuras de datos de este tipo son: a) Arreglo b) Multipila c) Cola doble d) Conjuntos En esta ocasin solo veremos las estructuras de datos del tipo a).

Arreglos:
Organizacin Lgica: Dimensiones. Lmite inferior y superior de cada dimensin. Tipo de elementos. Organizacin Fsica: Direccin inicial (de un intervalo de memoria) Tamao de los elementos. Orden de las dimensiones. Desplazamientos Las operaciones con los arreglos son: a) Recuperacin de uno de sus elementos. b) Actualizacin de un elemento. Ambas operaciones se realizan en funcin de los ndices que sealan la ubicacin del elemento. Arreglos unidimensionales Para propsitos de claridad tomemos la declaracin siguiente: Sea A un ARREGLO [1..20] con elementos de tipo Caracter Reserva un intervalo de memoria de 20 lugares consecutivos a partir de una direccin que denotaremos por DirA. A[1] A[2] A[3] A[4] DirA

DirA+1 DirA+2 DirA+3

Intervalo de memoria [ DirA, DirA + 19 ]

52

Algoritmos y Estructuras de Datos

Suponiendo que el direccionamiento sea a nivel de bytes. Las operaciones a) y b) se reducen al clculo del as llamado Polinomio de Direccionamiento (Pd). El Pd obtiene la direccin absoluta de un elemento del arreglo dados sus ndices. Por ejemplo: Si nos referimos a A[7] Pd (A[7]) = DirA + 6 Pd (A[x]) = DirA + x-1. En general se tiene que si B es un ARREGLO [1..5] con elementos de tipo T Pd (B[x]) = DirB + (x-1) lt; con lt igual a la longitud en bytes asignada al tipo T. NOTA: Se puede ver que en el arreglo unidimensional no interviene la cantidad de elementos para encontrar la direccin absoluta.

Arreglos Bidimensionales:
A estos arreglos se les asocia: Un intervalo de memoria a cada dimensin. Por tanto el Pd depende del orden en que se tome cada dimensin. Existen dos ordenes a saber: i) ii) Por ejemplo: Sea C un ARREGLO [1..n] [1..m] con elementos de tipo Carcter para la referencia C[k1][ k2] considerando el orden por columnas, su direccin absoluta es: Pd(C[k1][k2]) = Dirc + n*(k2-1) + (k1-1) 1 2 Por columnas; variando primero el segundo ndice. Por renglones; variando primero el primer ndice.

........

En el caso general:

. .
........

. . .
53

Algoritmos y Estructuras de Datos

Sea E un ARREGLO [i1.. s1][ i2 .. s2] ... [in .. sn] con elementos de tipo T Donde ij, sj lmites inferior y superior, respectivamente de la j-sima dimesin. rj = sj ij + 1 rango de la j-sima dimensin. n - nmero de dimensiones. T tipo de los elementos. Orden de las dimensiones todas las posibles formas de variar n-dimensiones. Si tomamos en orden decreciente las dimensiones para el almacenamiento (se almacena primero la ltima dimensin, luego la penltima, as sucesivamente hasta almacenar la primera) tenemos que: Pd(E[k1][ k2]...[ kn]) = DirE + ((kn 1) * rn-1* rn-2* ...* r1 + (kn-1 1) * rn-2* rn-3* ...* r1 + ...+

(k2-1) * r1 + k1 1) * lt

Aplicaciones de los Polinomios de Direccionamiento en los Arreglos Regulares


Existen matrices, empleadas en Algebra Lineal Numrica, con estructura especial en donde se puede aprovechar el Pd para almacenarlas; por ejemplo, supongamos que queremos manejar una matriz triangular inferior almacenada por columnas de orden n. Se puede optimizar el almacenamiento de esta matriz empleando un arreglo y el Pd correspondiente. Si la almacenamos por columnas, a partir de la segunda columna debemos descontar una cantidad de elementos iguales a cero que no se requiere almacenar, as tendremos que el nmero de elementos a descontar ser: uno para la segunda, dos para la 3ra, etc. Tales elementos se acumulan con respecto a la k2-sima columna.
k 2 1

Pd (m[k1][ k2]) = Dirm + n * (k2-1) + (k1 1) - i


i =1

= Dirm + n * (k2 - 1) + (k1 1) k2 * (k2 1) 2

x x x x x

0 x x x x

0 0 x x x

0 0 0 x x

0 0 0 0 x

2.4 Tipos Abstractos de Datos (TADs) Como se ha podido constatar en las Secciones anteriores, es necesario realizar un anlisis de la Informacin antes de poder procesarla, dicho anlisis produce, entre otras cosas, cuales son los elementos necesarios para poder organizar la informacin. De aqu que tengamos que, son las Estructuras de Datos las que nos permiten llevar a cabo tal organizacin, sin embargo el anlisis antedicho no est completo si no se considera la forma en que cada estructura de datos integra sus operaciones para trabajar con llas. Lo anterior nos conduce a un concepto ms amplio de estructuras de datos, el de los Tipos Abstractos de Datos (TAD). 54

Algoritmos y Estructuras de Datos

Un TAD consiste en la separacin clara entre: a) Su implantacin, y b) Su uso, a travs de su interfaz, que es la parte visible para el usuario terminal del TAD. Lo anterior implica que un TAD tiene una interfaz y puede tener varias implantaciones. La definicin de un TAD se hace en dos partes: i) La especificacin, y ii) La implantacin. Una especificacin formal consiste en determinar o explicar en trminos formales (matemticos) lo que se desee, en nuestro caso un tipo de dato que es necesario para la resolucin de un problema. Consideremos a T, como tal tipo de dato, el cual se define "como una clase de valores y una coleccin de operaciones sobre esos valores. Si las propiedades de esas operaciones son especificadas solo con axiomas, entonces T es un tipo abstracto de dato o una abstraccin de dato" [Gutt-78]. Una implantacin correcta del TAD cumple con cada uno de los axiomas especificados para l. La especificacin por axiomas algebraicos para el tipo T se compone de: i.1) Una especificacin sintctica: donde se definen los nombres, dominios y rangos de las operaciones sobre T, y i.2) Una especificacin semntica: compuesta del conjunto de axiomas en forma de ecuaciones, que dicen como opera cada una de las operaciones sobre las otras. La implantacin se compone de: ii.1) Una representacin: especifica como los valores del TAD sern almacenados en la memoria, es decir su estructura de datos, y ii.2) Los algoritmos: especifican como ser usada y manipulada la estructura de datos, es decir las operaciones del TAD.

55

Algoritmos y Estructuras de Datos

El acceso al TAD se hace a travs de su interfaz que es visible para los usuarios terminales de ella. La implantacin del TAD es invisible para el usuario terminal y es visible para el que desarrolla el TAD. Como ejemplo: Consideremos la especificacin del TAD : Pila, la cual se har para una pila no limitada. Tipo de dato: Pila[elemento:tipoEle] Especificacin sintctica: creaPila() -> Pila, meterElePila(Pila,tipoEle) -> Pila, sacarElePila(Pila) -> Pila, conTopePila(Pila) -> tipoEle V {TipoEleNoDef}, vacaPila(Pila) -> Lgico, destruyePila(Pila) -> . Crea la Pila Inserta un nuevo elemento en el tope Elimina el elemento que est en el tope Consulta el elemento que est en el tope Verifica si la pila est vaca Destruye la pila

Especificacin semntica: Declaracin: P: Pila, el: tipoEle; sacarElePila(creaPila()) = creaPila() conTopePila(creaPila()) = {TipoEleNoDef} conTopePila(meterElePila(P,el)) = el vacmaPila(creaPila()) = Verdadero vacmaPila(meterElePila(P,el)) = Falso

En la especificacin sintctica es posible ver claramente cules son las operaciones primitivas validas sobre la estructura y cules son los tipos de datos que cada una regresa, despus de efectuada la operacin. Del ejemplo, obsrvese la operacin sacarElePila() que se necesita para operar la Pila, devolviendo la misma Pila pero disminuida en un elemento, ya que suprime el elemento que est en el tope. Es en la especificacin en donde se incluyen aquellas operaciones que son bsicas para el TAD, es decir las operaciones que sirven de base para construir cualquier otra operacin sobre el tipo.

56

Algoritmos y Estructuras de Datos

Del ejemplo, (en la mayora de las estructuras de datos, sino es que en todas) es til tener una operacin que vace o limpie la estructura, en el caso de la Pila, sera la operacin vaciarPila(Pila)->Pila. Tal operacin no aparece en la especificacin del TAD Pila, debido a que puede ser "construida" invocando varias veces la operacin sacarElePila(Pila) hasta que vacaPila(Pila) sea verdadero. Por lo tanto, el nmero de las operaciones que aparecen en cualquier implantacin de un TAD no tiene por que ser igual, al nmero de las operaciones de su especificacin. En la especificacin semntica se determina el efecto que tiene cada una de las operaciones especificadas sobre las dems. Del ejemplo: Qu sucede si deseamos saber si la Pila est vaca, habindose creado recientemente?, esto es: vacmaPila(creaPila()), da como resultado Verdadero, ya que aunque la pila existe, y como ha sido recientemente creada, est vaca. En el caso de: conTopePila(creaPila()), el resultado es un valor especial denominado TipoEleNoDef, (tipoElemento no definido) para expresar que no puede regresarse elemento alguno, pues la pila est vaca. Este valor especial para "el" debe estar definido en la implantacin del TAD. En la prctica la especificacin puede hacerse escogiendo, una representacin fsica para el tipo Pila, que puede ser, por ejemplo escogiendo: - La secuencial. - La enlazada. Ambas implantaciones, deben tomar en cuenta que la memoria es finita, y por ello tal especificacin es tan solo para un TAD que diferir en algunos detalles de la implantacin realizada basndose en lla.

57

Algoritmos y Estructuras de Datos

3. Grafos
Introduccin El grafo es una estructura no lineal que debido a su generalidad tiene gran uso en diferentes aplicaciones de la ciencia y la ingeniera. Su aplicacin, adems de las estructuras de datos, se puede observar en los diferentes tipos de redes, como de computadoras, transportes, rutas reas, interconexin elctrica, aspectos relacionados en la economa e incluso en las redes neuronales artificiales se representan por medio de grafos.

3.1 Fundamentos y terminologa bsica Un grafo, G, es un par, compuesto por dos conjuntos V y A. Al conjunto V se le llama conjunto de nodos del grafo. A es un conjunto de pares de vrtices, estos pares se conocen habitualmente con el nombre de arcos o ejes del grafo. Se suele utilizar la notacin G = (V, A) para identificar un grafo. Los grafos representan un conjunto de objetos donde no hay restriccin a la relacin entre ellos. Son estructuras ms generales y menos restrictivas. Podemos clasificar los grafos en dos grupos: dirigidos y no dirigidos. En un grafo no dirigido el par de vrtices que representa un arco no est ordenado. Por lo tanto, los pares (v1, v2) y (v2, v1) representan el mismo arco. En un grafo dirigido cada arco est representado por un par ordenado de vrtices , de forma que y representan dos arcos diferentes. El arco, a diferencia del segmento, tiene un punto de origen y punto terminal, lo cual implica una direccin. Se representa grficamente mediante una flecha. Ejemplos de grafos (dirigidos y no dirigidos): a) Grafo 1 G1 = (V1, A1) V1 = {1, 2, 3, 4} b) Grafo 2 G2 = (V2, A2) V2 = {1, 2, 3, 4, 5, 6} c) Grafo 3 G3 = (V3, A3) V3 = {1, 2, 3}

A1 = {(1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)}

A2 = {(1, 2), (1, 3), (2, 4), (2, 5), (3, 6)}

A3 = { <1, 2>, <2, 1>, <2, 3> }

Grficamente estas tres estructuras de vrtices y arcos se pueden representar de la siguiente manera:

58

Algoritmos y Estructuras de Datos

a)Grafo 1

b) Grafo 2

c) Grafo 3

Los grafos permiten representar conjuntos de objetos arbitrariamente relacionados. Se puede asociar el conjunto de vrtices con el conjunto de objetos y el conjunto de arcos con las relaciones que se establecen entre ellos. Los grafos son modelos matemticos de numerosas situaciones reales: un mapa de carreteras, la red de ferrocarriles, el plano de un circuito elctrico, el esquema de la red telefnica de una compaia, etc. El nmero de distintos pares de vrtices (v(i), v(j)), con v(i) <> v(j), en un grafo con n vrtices es n*(n-1)/2. Este es el nmero mximo de arcos en un grafo no dirigido de n vrtices. Un grafo no dirigido que tenga exactamente n*(n-1)/2 arcos se dice que es un grafo completo. En el caso de un grafo dirigido de n vrtices el nmero mximo de arcos es n*(n-1).

Algunas definiciones bsicas de grafos:


Orden de un grafo: es el nmero de nodos (vrtices) del grafo. Grado de un nodo: es el nmero de ejes (arcos) que inciden sobre el nodo Grafo simtrico: es un grafo dirigido tal que si existe la relacin entonces existe , con u, v pertenecientes a V. Grafo no simtrico: es un grafo que no cumple la propiedad anterior. Grafo reflexivo: es el grafo que cumple que para todo nodo u de V existe la relacin (u, u) de A. Grafo transitivo: es aqul que cumple que si existen las relaciones (u, v) y (v, z) de A entonces existe (u, z) de A.

59

Algoritmos y Estructuras de Datos

Grafo completo: es el grafo que contiene todos los posibles pares de relaciones, es decir, para cualquier par de nodos u, v de V, (u <> v), existe (u, v) de A. Camino: un camino en el grafo G es una sucesin de vrtices y arcos: v(0), a(1), v(1), a(2), v(2), ... , a(k), v(k); tal que los extremos del arco a(i) son los vrtices v(i-1) y v(i). Longitud de un camino: es el nmero de arcos que componen el camino. Camino cerrado (circuito): camino en el que coinciden los vrtices extremos (v(0) = v(k)). Camino simple: camino donde sus vrtices son distintos dos a dos, salvo a lo sumo los extremos. Camino elemental: camino donde sus arcos son distintos dos a dos. Camino euleriano: camino simple que contiene todos los arcos del grafo. Grafo euleriano: es un grafo que tiene un camino euleriano cerrado. Grafo conexo: es un grafo no dirigido tal que para cualquier par de nodos existe al menos un camino que los une. Grafo fuertemente conexo: es un grafo dirigido tal que para cualquier par de nodos existe un camino que los une. Punto de articulacin: es un nodo que si desaparece provoca que se cree un grafo no conexo. Componente conexa: subgrafo conexo mxima de un grafo no dirigido (parte ms grande de un grafo que sea conexa).

Tipo de datos abstracto de un grafo dirigido conexo El TDA de un grafo dirirgido conexo se puede representar de la siguiente manera: TDA Grafo_dirigido_ conexo (Elementos E, Operadores O, Axiomas A)

Los elementos bsicos son: Un conjunto de nodos {N1,N2,N3,N4,...Nk} Un conjunto de arcos {ai,j} cada arco nace en el nodo i y termina en el nodo j. Un conjunto de pesos {pi,j} asociado a cada arco ai,j existe un peso pi,j que implica un costo

60

Algoritmos y Estructuras de Datos

Un conjunto de datos {Dato i}asociado a cada nodo i. Referencias (apuntadores) de entrada. ENTRADA Indicador nulo Operadores: unin (U) y diferencia (-)

Los operadores para el manejo del grafo : Iniciar G(N,A), G(N,A) (G(N,A) G(N,A) (G(N,A)

Insertar_Nodo (G(N,A),Ni) Insertar_Arco (G(N,A),ai,j) Eliminar_Nodo (G(N,A),Ni) Eliminar_Arco (G(N,A),ai,j) Consultar (G(N,A)Ni) Recorrer G(N,A) Dato

{N1,N2,N3,N4,...Nk}

Los axiomas para un grafo dirigido conexo para el que su ltimo nodo es K ESTADO (INICIAR) = VACIO INSERTAR (INICIAR, N1) = G(N,A) INSERTAR_NODO ( G(N,A),Nk) = G(N,A) ENTRADA = ENTRADA = Liga N1 Es conexo si para 1<=i<k, 1<=j<k { ai,j} U { ak,j}#0 ELIMINAR_NODO (G(N,A),N1) = G(N,A) Generar error si N1 { Ni} Implica que para 1<=i <=k, 1<=j<=k { ai,j} U { ak,j}=0 INSERTAR_ARCO (G(N,A),al,m) = G(N,A)
Implica que

N 1 { N i} y N m { N i}

ELIMINAR_ARCO (G(N,A),al,m) ) Generar error si al,m { ai,j} Implica que para 1<=i <=k, 1<=j<=k { ai,l} U { a1,,j} #0 y { ai,m} U { am,,j} # 0

61

Algoritmos y Estructuras de Datos

3.2 Representacin de grafos Existen tres maneras bsicas de representar los grafos: mediante matrices de adyacencia, listas de adyacencia y matrices dispersas. Cada representacin tiene unas ciertas ventajas e inconvenientes respecto de las dems, que comentaremos ms adelante. 3.2.1 Representacin mediante matrices de adyacencia Un grafo es un par compuesto por dos conjuntos: un conjunto de nodos y un conjunto de relaciones entre los nodos. La representacin tendr que ser capaz de guardar esta informacin en memoria. La forma ms fcil de guardar la informacin de los nodos es mediante la utilizacin de un vector que indexe los nodos, de manera que los arcos entre los nodos se pueden ver como relaciones entre los ndices. Esta relacin entre ndices se puede guardar en una matriz, que llamaremos de adyacencia. 1. Definir el mximo de nodos= MAX; 2. Indice =1-MAX; Valor_Nodo = ??; Valor_Arco = ??; Clase Arco { Info: Valor_Arco; // informacin asociada a cada arco *) Existe: Boolean; } clase Nodo { Info: Valor_Nodo; // informacin asociada a cada nodo Existe: Boolean; } Clase Grafo { MATRIZ [MAX][MAX] } Con esta representacin tendremos que reservar al menos del orden de (n^2) espacios de memoria para la informacin de los arcos, y las operaciones relacionadas con el grafo implicarn, habitualmente, recorrer toda la matriz, con lo que el orden de las operaciones ser, en general, cuadrtico, aunque tengamos un nmero de relaciones entre los nodos mucho menor que (n^2).

62

Algoritmos y Estructuras de Datos

En cambio, con esta representacin es muy fcil determinar, a partir de dos nodos, si estn o no relacionados: slo hay que acceder al elemento adecuado de la matriz y comprobar el valor que guarda. Ejemplo:

Figura 3.1 Grafo no dirigido Supongamos el grafo representado en la figura 3.1. A partir de ese grafo la informacin que guardaramos, con esta representacin, sera:

Tabla 3.1 Matriz de adyacencia del grafo de la figura 3.1 3.2.2 Representacin mediante punteros: listas de adyacencia En las listas de adyacencia se intenta evitar justamente el reservar espacio para aquellos arcos que no contienen ningn tipo de informacin. El sustituto obvio a los vectores con huecos son las listas. 63

Algoritmos y Estructuras de Datos

En las listas de adyacencia lo que haremos ser guardar por cada nodo, adems de la informacin que pueda contener el propio nodo, una lista dinmica con los nodos a los que se puede acceder desde l. La informacin de los nodos se puede guardar en un vector, al igual que antes, o en otra lista dinmica. Si elegimos la representacin en un vector para los nodos, tendramos la siguiente definicin de grafo en Pascal: Const MAX_NODOS = ??; Type Indice = 1..MAX_NODOS; Valor_Nodo = ??; Valor_Arco = ??; Punt_Arco = ^Arco; Arco = Record Info: Valor_Arco; (* informacin asociada a cada arco *) Destino: Indice; Sig_Arco: Punt_Arco; end; Nodo = Record Info: Valor_Nodo; informacin asociada a cada nodo *) Existe: Boolean; Lista_Arcos: Punt_Arco; end; Grafo = Record Nodos: Nodo; end; Array[Indice] (*

of

En general se est guardando menor cantidad de elementos, slo se reservar memoria para aquellos arcos que efectivamente existan, pero como contrapartida estamos guardando ms espacio para cada uno de los arcos (estamos aadiendo el ndice destino del arco y el puntero al siguiente elemento de la lista de arcos). Las tareas relacionadas con el recorrido del grafo supondrn slo trabajar con los vrtices existentes en el grafo, que puede ser mucho menor que (n^2). Pero

64

Algoritmos y Estructuras de Datos

comprobar las relaciones entre nodos no es tan directo como lo era en la matriz, sino que supone recorrer la lista de elementos adyacentes perteneciente al nodo analizado. Adems, slo estamos guardando realmente la mitad de la informacin que guardbamos en el caso anterior, ya que las relaciones inversas (las relaciones que llegan a un cierto nodo) en este caso no se guardan, y averiguarlas supone recorrer todas las listas de todos los nodos. 3.2.3 Representacin mediante referencias: matrices dispersas o estructuras tipo red o malla Para evitar uno de los problemas que tenamos con las listas de adyacencia, que era la dificultad de obtener las relaciones inversas, podemos utilizar las matrices disperas, que contienen tanta informacin como las matrices de adyacencia, pero, en principio, no ocupan tanta memoria como las matrices, ya que al igual que en las listas de adyacencia, slo representaremos aquellos enlaces que existen en el grafo. Estructura Nodo Datos_Nodo Conexin_nodo1 Nmero_nodo Valor_arco Liga_1 Conexin_nodo2 Nmero_nodo Valor_arco Liga_2 Conexin_nodo_n Fin_Estructura

3.3 Recorrido de grafos Recorrer un grafo supone intentar alcanzar todos los nodos que estn relacionados con uno dado que tomaremos como nodo de salida. Existen bsicamente dos tcnicas para recorrer un grafo: el recorrido en anchura; y el recorrido en profundidad.

3. 3. 1 Propiedades: 1) El n de recorridos de longitud k de vi a vj es el elemento ij de la matriz M(G)k. 2) Un grafo G es bipartito G no tiene ciclos de longitud impar. 3) Si G tiene slo dos vrtices impares existe un camino entre ellos.

65

Algoritmos y Estructuras de Datos

Un grafo es conexo si para cada par de vrtices u y v existe un camino de u a v. Si G es un grafo no conexo (o disconexo), cada uno de sus subgrafos conexos maximales se llama componente conexa de G. Designaremos por k(G) al n de componentes conexas del grafo G Un vrtice v se llama vrtice-corte (o punto de articulacin) de G si el grafo G{v} tiene ms componentes conexas que G. Una arista a de un grafo G se llama puente si G-{a} tiene ms componentes conexas que G. 4)Los bloques de un grafo G son los subgrafos de G sin vrtices-corte y maximales con respecto a esta propiedad. 3.3. 2 Recorrido en anchura o BFS (Breadth First Search) El recorrido en anchura supone recorrer el grafo, a partir de un nodo dado, en niveles, es decir, primero los que estn a una distancia de un arco del nodo de salida, despus los que estan a dos arcos de distancia, y as sucesivamente hasta alcanzar todos los nodos a los que se pudiese llegar desde el nodo salida. El algoritmo general de recorrido es el siguiente: Algoritmo Recorrido_en_Anchura (BFS) Entradas gr: Grafo nodo_salida: Indice Variables queue: Cola de Indice aux_nod1, aux_nod2: Indice Inicio Iniciar_Cola(queue) Procesar(nodo_salida) Visitado(nodo_salida) <-- CIERTO Encolar(queue, nodo_salida) mientras NO Cola_Vacia(queue) hacer aux_nod1 <-- Desencolar(queue) para (todos los nodos), aux_nod2, adyacentes a aux_nod1 hacer si NO Visitado[aux_nod2] entonces Procesar(aux_nod2) Visitado[aux_nod2] <-- CIERTO Encolar(queue, aux_nod2) (* grafo a recorrer *) (* origen del recorrido *)

66

Algoritmos y Estructuras de Datos

fin_si fin_para fin_mientras Fin La diferencia a la hora de implementar el algoritmo general para cada una de las implementaciones de la estructura de datos grafo, residir en la manera de averiguar los diferentes nodos adyacentes a uno dado. En el caso de las matrices de adyacencia se tendrn que comprobar si los enlaces entre los nodos existen en la matriz. En los casos de las listas de adyacencia y de las matrices dispersas slo habr que recorrer las listas de enlaces que parten del nodo en cuestin para averiguar qu nodos son adyacentes al estudiado. 3.3.3Recorrido en profundidad o DFS (Depth First Search) A diferencia del algoritmo anterior, el recorrido en profundidad trata de buscar los caminos que parten desde el nodo de salida hasta que ya no es posible avanzar ms. Cuando ya no puede avanzarse ms sobre el camino elegido, se vuelve atrs en busca de caminos alternativos, que no se estudiaron previamente. El algoritmo es similar al anterior, pero utilizando, para guardar los nodos accesibles desde uno dado, una pila en lugar de una cola. Algoritmo Recorrido_en_Profundidad (DFS) Entradas gr: Grafo nodo_salida: Indice Variables stack: Pila de Indice aux_nod1, aux_nod2: Indice Inicio Iniciar_Pila(stack) Procesar(nodo_salida) Visitado(nodo_salida) <-- CIERTO Apilar(stack, nodo_salida) mientras NO Pila_Vacia(stack) hacer aux_nod1 <-- Desapilar(stack) para (todos los nodos), aux_nod2, adyacentes a aux_nod1 hacer (* grafo a recorrer *) (* origen del recorrido *)

67

Algoritmos y Estructuras de Datos

si NO Visitado[aux_nod2] entonces Procesar(aux_nod2) Visitado[aux_nod2] <-- CIERTO Apilar(stack, aux_nod2) fin_si fin_para fin_mientras Fin La utilizacin de la pila se puede sustituir por la utilizacin de la recurrencia, de manera que el algoritmo quedara como sigue: Algoritmo Recorrido_en_Profundidad (DFS) Entradas gr: Grafo nodo_salida: Indice Variables aux_nod2: Indice Inicio Procesar(nodo_salida) Visitado(nodo_salida) <-- CIERTO para (todos los nodos), aux_nod2, adyacentes a aux_nod1 hacer si NO Visitado[aux_nod2] entonces Recorrido_en_Profundidad(gr, aux_nod2) fin_si fin_para Fin (* grafo a recorrer *) (* origen del recorrido *)

68

Algoritmos y Estructuras de Datos

3.4 Caminos mnimos en grafos Uno de los objetivos de tener un grafo es poder analizar los desplazamientos desde cualquier nodo a los dems que conforman el grafo. Fundamentalmente, el planteamiento consiste que a partir de un nodo i del grafo, encontrar los recorridos ptimos para ir a cada uno de los nodos restantes, a partir de la base de que los arcos pueden representar, adems la existencia de la conexin, el costo o la distancia para desplazarse de un nodo a otro. Existen varias formas de alcanzar la solucin utilizando uno de los siguientes algoritmos: 1. Algoritmo de DijKstra 2. Algoritmo de Floyd 3. Algoritmo de Ford Distancia en un grafo: Sean G=(V,A) un grafo (o digrafo) ponderado, u, v vrtices de G. Se llama distancia de u a v, d(u,v), a la mnima longitud de los caminos que unen u con v. Si no existe camino de u a v se dice que d(u,v)=

Propiedades:
Si las aristas (o arcos, en el caso dirigido) no reciben pesos negativos entonces 1. d(x,y) 0 y d(x,y)=0 si y slo si x=y 2. d(x,y)=d(y,x) 3. d(x,y)+d(y,z) d(x,z)

Nociones relacionadas con distancia: La excentricidad de un vrtice v es e(v) = mx{d(v,z)/ z V(G)} El radio de un grafo es rad(G) = mn{e(v)/ v V(G)} El dimetro de un grafo es diam(G) = mx{e(v)/ v V(G)} El centro de un grafo G es el subgrafo inducido por el conjunto de vrtices de excentricidad mnima. La distancia total de un vrtice v es dt(v) = La mediana de un grafo G es el subgrafo inducido por el conjunto de vrtices de distancia total mnima

69

Algoritmos y Estructuras de Datos

e(b)=3, e(a)=e(c)=2

Centro <a>, Mediana <b>

Figura 3.2 Caminos mnimos

Propiedades:
1. Si G es un grafo conexo entonces rad(G) diam(G) 2rad(G) 2. Todo grafo es el centro de un grafo conexo 3. El centro de un rbol T est formado por uno o dos vrtices de T

3. 4.1 Algoritmos de Caminos Mnimos

Dado un grafo (o digrafo) ponderado y dos vrtices s y t se quiere hallar d(s,t) y el camino con dicha longitud. Los primeros algoritmos que presentamos obtienen todos los caminos de longitud mnima desde un vrtice dado s al resto de vrtices del grafo. El ltimo algoritmo resuelve el problema para un par cualquiera de vrtices de G. Si el vrtice u se encuentra en un camino C de longitud mnima entre los vrtices s y z entonces, la parte de C comprendida entre los vrtices s y u es un camino de longitud mnima entre s y u. Por tanto, el conjunto de caminos mnimos desde s a los restantes vrtices del grafo G es un rbol, llamado el rbol de caminos mnimos desde s. 3.4.1.1 Algoritmo de Dijkstra (1959) La idea bsica del algoritmo es la siguiente: Si P es un camino de longitud mnima s--z y P contiene al vrtice v, entonces la parte s--v de P es tambin camino de longitud mnima de s a v. Esto sugiere que si deseamos determinar el camino ptimo de s a cada vrtice z de G, podremos hacerlo en orden creciente de la distancia d(s,z)

70

Algoritmos y Estructuras de Datos

Descripcin del algoritmo Entrada: Un grafo (o digrafo) ponderado, un vrtice s V. El peso de la arista uv se indica por w(uv), poniendo w(uv)= si uv no es arista. (Las aristas tienen pesos no negativos) Clave: Mantener el conjunto T de vrtices para el que se conoce el camino ms corto y ampliar T hasta que T=V. Para ello etiquetamos cada vrtice z con t(z) que es la longitud del camino ms corto ya encontrado. Mantenemos un conjunto A de vrtices que se encuentran en el rbol de bsquea pero no en el rbol de camino mnimo y que son alcazables desde los vrtices de T. Inicializacin: Sea A={s}, T{}, t(s)=d(s,s)=0, t(z)= para z s. Iteracin: Elegir el vrtice v A con etiqueta mnima. Aadir v a T. Designar v como "Vrtice Actual" y eliminarlo de A Analizar cada arista vz con z T y actualizar la etiqueta de z a min{t(z), t(v)+w(vz)}. Aadir z a A. La iteracin continua hasta que T=V(G) o hasta que t(z)= para cada vrtice z T En cualquier caso la etiqueta de cada vrtice z en T ser la distancia de s a z. En el segundo caso los vrtices que no estn en T no son accesibles desde s.

Procedimiento
1. Se utilizan como estructuras adicionales los vectores D y R, de taamo N, para almacenar los desplazamientos. 2. Se determinan los valores de caminos entre el nodo Ni y los nodos adyacentes, a los que no estn conectados se les coloca un valor infinito y se incluyen en el vector de desplazamiento, {Di,1 , Di,2 , Di,3,.Di,m }. 3. Se selecciona el menor valor Di,j, este es el resultado optimo para ir del nodo i al nodo j . 4. Se determinan las distancias del nodo j a sus nodos adyacentes, Rj,k incluido Ri,j, se calcula el valor de los recorridos Ri,k = Ri,j +Rj,k.
5.

Se comparan los valores del vector base de desplazamiento D con R y se deja en el vector de desplazamiento del menor entre Dj,k y Ri,k.

6. Se regresa al segundo paso, y el algoritmo se termina cuando se han seleccionado todos los N-1 nodos.

71

Algoritmos y Estructuras de Datos

Ejemplo 4 2 3 2

10

4 5 5

3 1 12

Figura 3.3 Grafo dirigido Encontrar el desplazamiento mnimo para ir del nodo 1 a los dems nodos del grafo mostrado en la figura 3.3. 1. Se determinan los desplazamientos Di,j, de la primera fila, para ello se coloca cuando no hay arcos. Estos valores son mostrados en la tabla 3.2 Del vector base se selecciona el menor. Valor D1,2 =3 Se obtiene de esta forma el mnimo desplazmiento entre el nodo1 y el nodo2. Se sale del nodo2. 2. Se buscanlos nodos adyacentes del nodo2, que son nodo3 y nodo5. 3. Se calcula R1,3 y R 1,5 : y R1,5 = 3+8 R1,3 = 3+4 Se pasa por N2 4. Se compara con D1,3 y D1,5, con R1,3 y R 1,5, y se sustituye el vector de desplazamiento aquellos que sean menores 5. Se selecciona el mnimo y se reemplaza en el vector base 6. Se continua el proceso hasta ciando se haya determinado el mnimo en todas las distancias. D2 3 3 3 3 D3 7 7 7 D4 10 10 9 9 D5 12 11 11 11 Nodo salida Distancia 2 3 3 7 4 9 5 11

Tabla 3.2 Distancias mnimas del grafo dirigido

72

Algoritmos y Estructuras de Datos

Anlisis de la complejidad En cada iteracin se aade un vrtice a T, luego el n de iteraciones es n. En cada una se elige una etiqueta mnima, la primera vez entre n-1, la segunda entre n-2, ..., luego la complejidad total de estas elecciones es O(n2). Por otra parte cada arista da lugar a una actualizacin de etiqueta, que se puede hacer en tiempo constante O(1), en total pues O(q). Por tanto la complejidad del algoritmo es O(n2) Teorema (Validez del algoritmo) El algoritmo de Dijkstra calcula d(s,z) para cada vrtice z V(G) Demostracin Debemos probar que la etiqueta definitiva t(z) es d(s,z). Sean x1, x2, ..., xn los vrtices de G ordenados por su incorporacin al conjunto T. As x1=s. Vamos a demostrar el resultado por induccin sobre i. Primer paso) El resultado es cierto para i=1, pues x1=s y sabemos que d(s,s)=0=t(s) Paso de i a i+1) La hiptesis de induccin es que t(x1)=d(s,x1), ..., t(xi)=d(s,xi). Debemos probar que t(xi+1)=d(s,xi+1)

Llamemos S={x1, x2, ..., xi}, La etiqueta t(xi+1) es, por la construccin del algoritmo, la longitud de un camino Q s,..., u, xi+1, donde u es el ltimo vrtice en S y e=uxi+1 es una arista de G. Si hay otro camino Q' de s a xi+1 debemos probar que long(Q) long(Q'). Sea z el primer vrtice de Q' fuera de S, vz la primera arista y Q'' el resto del camino de z a xi+1 long(Q')=d(s,v)+w(vz) +long(Q'') t(z)+long(Q'') y como xi+1 se elige como vrtice de menor etiqueta ser t(z) t(xi+1) y as long(Q') t(xi+1)+long(Q'') t(xi+1) por ser todas las aristas de peso no negativo. Por tanto t(xi+1)=long(Q)=d(s,xi+1)

3.4.1.2 Algoritmo de Ford (1956) Es una variante del algoritmo de Dijkstra que admite la asignacin de pesos negativos en los arcos, aunque no permite la existencia en el digrafo de ciclos de peso negativo.

73

Algoritmos y Estructuras de Datos

Descripcin del algoritmo Entrada: Un digrafo ponderado con pesos no negativos en los arcos, un vrtice s V. El peso del arco uv se indica por w(uv), poniendo w(uv)= si uv no es arco. Salida: La distancia desde s a cada vrtice del grafo Clave: Mejorar en cada paso las etiquetas de los vrtices, t(u) Inicializacin: Sea T={s}, t(s)=d(s,s)=0, t(z)= para z s. Iteracin: Mientras existan arcos e=xz para los que t(z)>t(x) + w(e) actualizar la etiqueta de z a min{t(z), t(x)+w(xz)} Anlisis de la complejidad En primer lugar debemos observar que cada arco puede considerarse varias veces. Empecemos ordenando los arcos del digrafo D siendo este el orden en que se considerarn los arcos en el algoritmo. Despus de la primera pasada se repite el proceso hasta que en una pasada completa no se produzca ningn cambio de etiquetas. Si D no contiene ciclos negativos puede demostrarse que, si el camino mnimo s u contiene k arcos entonces, despus de k pasadas se alcanza la etiqueta definitiva para u. Como k n y el n de arcos es q, resulta que la complejidad del algoritmo de Ford es O(qn). Adems podemos detectar un ciclo negativo si se produce una mejora en las etiquetas en la pasada nmero n.

3.4.1.3 Algoritmo de Floyd (1962) A veces no es suficiente calcular las distancias con respecto a un vrtice s, si no que necesitamos conocer la distancia entre cada par de vrtices. Para ello se puede aplicar reiteradamente alguno de los algoritmos anteriores, variando el vrtice s de partida. As tendramos algoritmos de complejidad O(n3) (si usamos el algoritmo de Dijkstra) u O(n2q) (si usamos el algoritmo de Ford). A continuacin se describe un algoritmo, debido a Floyd y Warshall, con una estructura sencilla, que permite la presencia de arcos de peso negativo y que resuelve el mismo problema. (Naturalmente los ciclos de peso negativo siguen estando prohibidos). La idea bsica del algoritmo es la construccin de una sucesin de matrices W0, W1, ..., Wn, donde el elemento ij de la matriz Wk nos indique la longitud del camino mnimo i j utilizando como vrtices interiores del camino los del conjunto {v1, v2, ..., vk}. La matriz W0 es la matriz de pesos del digrafo, con w0ij=w(ij) si existe el arco i j, w0ii=0 y w0ij= si no existe el arco i j. Descripcin del algoritmo Entrada: Un digrafo ponderado sin ciclos de peso negativos. El peso del arco uv se indica por w(uv), poniendo w(uv)= si uv no es arco.

74

Algoritmos y Estructuras de Datos

Salida: La distancia entre dos vrtices cualesquiera del grafo Clave: Construimos la matriz Wk a partir de la matriz Wk-1 observando que i,j=1,...,n Iteracin: Para cada k =1, ..., n, hacer El elemento ij de la matriz Wn nos da la longitud de un camino mnimo entre los vrtices i y j Anlisis de la complejidad Se deben construir n matrices de tamao nxn y cada elemento se halla en tiempo constante. Por tanto, la complejidad del algoritmo es O(n3) Validez del algoritmo La indicacin dada en la clave se puede demostrar por induccin sobre k, con lo que tendramos demostrada la validez del algoritmo Observaciones: 1. Si existe un ciclo negativo entonces algn elemento de la diagonal principal se har negativo en alguna de las matrices Wk 2. Si adems de las longitudes de los caminos mnimos se desean obtener dichos caminos basta construir una sucesin auxiliar de matrices P0, P1,..., Pn de la siguiente forma: El elemento ij de la matriz P0 es j Si en el elemento ij de la matriz Wk no se produce cambio entonces pkij coincide con el elemento correspondiente de la matriz Pk-1 . Si hay cambio entonces pkij = pk-1ik As el elemento ij de la matriz Pn indica el primer vrtice despus de vi en el camino de longitud mnima que conecta los vrtices vi y vj. Y con esto resulta fcil reconstruir todo el camino.

75

Algoritmos y Estructuras de Datos

1.

Programa para representar un grafo conexo mediante una matriz

import java.io.*; public class grafi{ String nombre[]=new String[20]; int matriz[][]=new int[20][20]; int n,l; grafi(){ for(int i=0;i<n;i++) for(int j=0;j<n;j++) matriz[i][j]=0; } void lista(){ for(int i=0;i<n;i++) System.out.println(i+"."+nombre[i]+" ");} void pideDimension()throws IOException{ do{ System.out.println("Cual es la dimension de tu matriz? max 20"); BufferedReader in=new BufferedReader(new InputStreamReader(System.in)); n=Integer.parseInt(in.readLine()); }while(n>20||n<2); } void pideN()throws IOException{ BufferedReader in=new BufferedReader(new InputStreamReader(System.in)); System.out.println("Proporciona identificadores de los vertices"); for(int i=0;i<n;i++) nombre[i]=in.readLine(); } void pideMz()throws IOException{ BufferedReader in=new BufferedReader(new InputStreamReader(System.in)); for(int i=0;i<n;i++) for(int j=0;j<n;j++){ if(i!=j) if(matriz[i][j]==0){ System.out.println("Cual es el valor del eje "+nombre[i]+" con respecto a "+nombre[j]+" ?, si no existe escribe un numero <1"); matriz[i][j]=matriz[j][i]=Integer.parseInt(in.readLine()); if(matriz[i][j]>0) l++; } } } void muestra(){ for(int i=0;i<n;i++) System.out.print(" "+nombre[i]); System.out.print("\n"); for(int i=0;i<n;i++){ System.out.print(nombre[i]); for(int j=0;j<n;j++) System.out.print(" "+matriz[i][j]+" "); System.out.print("\n"); } }

76

Algoritmos y Estructuras de Datos

void escriOrden(){ System.out.println("Orden del grafo "+n);} void grado(int s){ boolean f=false; int cont=0; for(int i=0;i<n;i++) if(i==s) f=true; if(!f) System.out.println("El vertice no existe"); else{ System.out.print("El grado del vertice es "); for(int i=0;i<n;i++) if(i==s) for(int j=0;j<n;j++){ if(matriz[i][j]>0) cont++; System.out.println(cont); } } } void conVertice(){ System.out.print("V={"); for(int i=0;i<n;i++) System.out.print(nombre[i]+","); System.out.print("}\n"); } void conArcos(){ System.out.print("A={"); for(int i=0;i<n/2;i++) for(int j=0;j<n;j++) if(matriz[i][j]>0) System.out.print("("+nombre[i]+","+nombre[j]+"),"); System.out.println("}"); } public static void main(String a[])throws IOException{ int op; BufferedReader in=new BufferedReader(new InputStreamReader(System.in)); grafi grafo=new grafi(); grafo.pideDimension(); grafo.pideN(); grafo.pideMz(); do{ System.out.println("Que deseas hacer?\n1.Ver matriz\n2.Ver orden del grafo\n3.Buscar grado de un vertice\n4.Salir,\n5.Ver conjunto de vertices\n6.Ver conjunto de Arcos"); op=Integer.parseInt(in.readLine()); switch(op){ case 1:grafo.muestra(); break; case 2:grafo.escriOrden(); break; case 3:System.out.println("Proporciona el numero del vertice a buscar el grado"); grafo.lista();

77

Algoritmos y Estructuras de Datos

grafo.grado(Integer.parseInt(in.readLine())); break; case 4:System.out.println("Finalizando..."); break; case 5:grafo.conVertice(); break; case 6:grafo.conArcos(); break; default:System.out.println("Opcion invalida"); } }while(op!=4); } }

78

Algoritmos y Estructuras de Datos

2. Programa que calcula los grados de los vrtices de un grafo dirigido en una representacin matricial
import java.io.*; public class grafis2{ String nombre[]=new String[20]; int matriz[][]=new int[20][20]; boolean matrix[][]=new boolean[20][20]; int n,l; grafis2(){ for(int i=0;i<n;i++) for(int j=0;j<n;j++){ if(i!=j) matriz[i][j]=10000; else matriz[i][j]=0;} } void floyd(){ int mat[][]=new int[20][20]; for(int i=0;i<n;i++) for(int j=0;j<n;j++) mat[i][j]=matriz[i][j]; for(int k=0;k<n;k++) for(int i=0;i<n;i++) for(int j=0;j<n;j++) if((mat[i][k]+mat[k][j])<mat[i][j]) mat[i][j]=mat[i][k]+mat[k][j]; for(int i=0;i<n;i++) System.out.print(" "+nombre[i]); System.out.print("\n"); for(int i=0;i<n;i++){ System.out.print(nombre[i]); for(int j=0;j<n;j++) System.out.print(" "+mat[i][j]+" System.out.print("\n"); } }

");

void lista(){ for(int i=0;i<n;i++) System.out.println(i+"."+nombre[i]+" ");} void pideDimension()throws IOException{ do{ System.out.println("Cual es la dimension de tu matriz? max 20"); BufferedReader in=new BufferedReader(new InputStreamReader(System.in)); n=Integer.parseInt(in.readLine()); }while(n>20||n<2); } void valor(int i,int j,int res,int x){ if(x>0){ if(res==1)

79

Algoritmos y Estructuras de Datos

matrix[i][j]=true; else matrix[i][j]=false; } } void gradoi(int s){ boolean f=false; int cont=0; for(int i=0;i<n;i++) if(i==s) f=true; if(!f) System.out.println("El eje no existe"); else{ System.out.print("El grado interno del vertice es "); for(int i=0;i<n;i++) if(i==s) for(int j=0;j<n;j++){ if(matriz[i][j]>0) if(matrix[i][j]==true) cont++; } System.out.println(cont); } } void gradoe(int s){ boolean f=false; int cont=0; for(int i=0;i<n;i++) if(i==s) f=true; if(!f) System.out.println("El eje no existe"); else{ System.out.print("El grado externo del vertice es "); for(int i=0;i<n;i++) if(i==s) for(int j=0;j<n;j++){ if(matriz[i][j]>0) if(matrix[i][j]==false) cont++; } System.out.println(cont); } } void pideN()throws IOException{ BufferedReader in=new BufferedReader(new InputStreamReader(System.in)); System.out.println("Proporciona identificadores de los vertices"); for(int i=0;i<n;i++) nombre[i]=in.readLine(); } void pideMz()throws IOException{ BufferedReader in=new BufferedReader(new InputStreamReader(System.in));

80

Algoritmos y Estructuras de Datos

int res,aux; for(int i=0;i<n;i++) for(int j=0;j<n;j++){ if(i!=j) if(matriz[i][j]==0){ do{ System.out.println("Cual es el valor del eje "+nombre[i]+" con respecto a "+nombre[j]+" ?"); aux=Integer.parseInt(in.readLine()); if(aux<0) System.out.println("Valor invalido, no se admiten negativos"); }while(aux<0); matriz[i][j]=aux; do{ System.out.println("El grafo es dirigido de "+nombre[i]+" con respecto a "+nombre[j]+" ?"); System.out.println("1.Si\n2.No"); res=Integer.parseInt(in.readLine()); }while(res<1||res>2); valor(i,j,res,matriz[i][j]); if(matriz[i][j]>0) l++; } } } void muestra(){ for(int i=0;i<n;i++) System.out.print(" "+nombre[i]); System.out.print("\n"); for(int i=0;i<n;i++){ System.out.print(nombre[i]); for(int j=0;j<n;j++) System.out.print(" "+matriz[i][j]+" "); System.out.print("\n"); } } void escriOrden(){ System.out.println("Orden del grafo "+n);} void grado(int s){ boolean f=false; int cont=0; for(int i=0;i<n;i++) if(i==s) f=true; if(!f) System.out.println("El eje no existe"); else{ System.out.print("El grado del vertice es "); for(int i=0;i<n;i++) if(i==s) for(int j=0;j<n;j++){ if(matriz[i][j]>0) cont++; } System.out.println(cont); }

81

Algoritmos y Estructuras de Datos

} void conVertice(){ System.out.print("V={"); for(int i=0;i<n;i++) System.out.print(nombre[i]+","); System.out.print("}\n"); } void conArcos(){ System.out.print("A={"); for(int i=0;i<n;i++) for(int j=0;j<n;j++) if(matriz[i][j]>0) System.out.print("("+nombre[i]+","+nombre[j]+"),"); System.out.println("}"); } public static void main(String a[])throws IOException{ int op; BufferedReader in=new BufferedReader(new InputStreamReader(System.in)); grafis2 grafo=new grafis2(); grafo.pideDimension(); grafo.pideN(); grafo.pideMz(); do{ System.out.println("Que deseas hacer?\n1.Ver matriz\n2.Ver orden del grafo\n3.Buscar grado de un vertice\n4.Salir\n5.Ver conjunto de vertices\n6.Ver conjunto de Arcos\n7.Buscar grado interno de un vertice\n8.Buscar grado externo de un vertice\n9.Algoritmo de Floyd"); op=Integer.parseInt(in.readLine()); switch(op){ case 1:grafo.muestra(); break; case 2:grafo.escriOrden(); break; case 3:System.out.println("Proporciona el numero del vertice a buscar el grado"); grafo.lista(); grafo.grado(Integer.parseInt(in.readLine())); break; case 4:System.out.println("Finalizando..."); break; case 5:grafo.conVertice(); break; case 6:grafo.conArcos(); break; case 7: System.out.println("Proporciona el numero del vertice a buscar el grado interno"); grafo.lista(); grafo.gradoi(Integer.parseInt(in.readLine())); break; case 8:System.out.println("Proporciona el numero del vertice a buscar el grado externo"); grafo.lista(); grafo.gradoe(Integer.parseInt(in.readLine())); break; case 9:grafo.floyd();

82

Algoritmos y Estructuras de Datos

break; default:System.out.println("Opcion invalida"); } }while(op!=4); } }

3. Crear un programa para la representacin de grafos mediante multilistas.


a) // Clase que crea nodos tipo principal import java.io.*; class grafin{ grafin liga; grafini sub; String identifica; grafin(){sub=null;} grafin(String nombre,grafini m){ this(nombre,null,m);} grafin(String nombre){ this(nombre,null,null);} grafin(String nombre,grafin r,grafini m){ liga=r; sub=m; identifica=nombre; } grafin getliga(){ return liga;} grafini getsub(){ return sub; } void setliga(grafin m){ liga=m;} void setsub(grafini dato){ sub=dato;} String setide(){ return identifica;}

b) // Clase que crea nodos secundarios para la representacin de grafos mediante listas
import java.io.*; class grafini{ grafini liga; String identifica; int valor; grafini(String nombre,int val,grafini m){ liga=m;

83

Algoritmos y Estructuras de Datos

identifica=nombre; valor=val; } grafini getliga(){ return liga;} void setliga(grafini m){ liga=m;} String setide(){ return identifica;} int getVal(){ return valor; } }

c) // Clase para el manejo de listas para la representacin de un grafo import java.io.*; class grafit{ int matrix[][]=new int[20][20]; String nombres[]=new String [20]; int num; private grafin cabeza; grafit(){ cabeza=null; num=0; for(int i=0;i<20;i++) for(int j=0;j<20;j++) matrix[i][j]=10000; } int numnod(){ int nod=0; grafin prev,sig; prev=cabeza; sig=cabeza; while(sig!=null){ nod++; prev=sig; sig=sig.getliga(); } return nod;} void fullmat(){ grafini prev,sig; grafin ant,pos; ant=cabeza; pos=cabeza; int i=0; int n=0; while(pos!=null){ ant=pos; pos=pos.getliga(); nombres[i]=ant.setide(); prev=ant.getsub(); sig=prev; while(sig!=null){

84

Algoritmos y Estructuras de Datos

prev=sig; for(int m=0;m<numnod();m++){ if(sig.setide().equals(nombres[m])) matrix[i][m]=prev.getVal(); if(i==m) matrix[i][m]=0;} sig=sig.getliga(); } i++;} } void floyd(){ fullmat(); int mat[][]=new int[20][20]; for(int i=0;i<numnod();i++) for(int j=0;j<numnod();j++) mat[i][j]=matrix[i][j]; for(int k=0;k<numnod();k++) for(int i=0;i<numnod();i++) for(int j=0;j<numnod();j++) if((mat[i][k]+mat[k][j])<mat[i][j]) mat[i][j]=mat[i][k]+mat[k][j]; for(int i=0;i<numnod();i++) System.out.print(" "+nombres[i]); System.out.print("\n"); for(int i=0;i<numnod();i++){ System.out.print(nombres[i]); for(int j=0;j<numnod();j++) System.out.print(" "+mat[i][j]+" System.out.print("\n"); } } void floyd2(){ fullmat(); int mat[][]=new int[20][20]; for(int i=0;i<numnod();i++) for(int j=0;j<numnod();j++) mat[i][j]=matrix[i][j]; for(int k=0;k<numnod();k++) for(int i=0;i<numnod();i++) for(int j=0;j<numnod();j++) if((mat[i][k]+mat[k][j])>mat[i][j]) mat[i][j]=mat[i][k]+mat[k][j]; for(int i=0;i<numnod();i++) System.out.print(" "+nombres[i]); System.out.print("\n"); for(int i=0;i<numnod();i++){ System.out.print(nombres[i]); for(int j=0;j<numnod();j++) System.out.print(" "+mat[i][j]+" System.out.print("\n"); } } void creaLis(String nom){ grafin p,prev,sig; grafini r=null;

");

");

85

Algoritmos y Estructuras de Datos

if(cabeza==null) cabeza=new grafin(nom); else{ prev=cabeza; sig=prev; while(sig!=null){ prev=sig; sig=sig.getliga(); } p=new grafin(nom); prev.setliga(p); } } void creaSub()throws IOException{ grafin p,prev,sig; sig=cabeza; prev=sig; while(sig!=null){ prev=sig; pide(sig); sig=sig.getliga(); } } void pide(grafin g)throws IOException{ int op; String aux,aux2; int val; int op2=2; BufferedReader in=new BufferedReader(new InputStreamReader(System.in)); System.out.println("Proporciona las relaciones con "+g.setide()); do{ do{ System.out.println("1.Introducir nuevo vertice\n2.Terminar con vertice principal"); op=Integer.parseInt(in.readLine()); }while(op>2||op<1); if(op==1){ do{ System.out.println("Introduce nombre del vertice a relacionar con "+g.setide()); aux2=g.setide(); aux=in.readLine(); if(!recoprin(aux)) System.out.println("Ese nodo es inexistente"); else if(!recopron(aux,aux2,g)) System.out.println("Ese nodo ya fue relacionado"); System.out.println("Deseas continuar con este eje?\n1.No\n(cualquier numero).Si"); op2=Integer.parseInt(in.readLine()); }while((!recoprin(aux)||!recopron(aux,aux2,g))&&op2!=1); if(op2!=1){ do{ System.out.println("Introduce valor del eje, mayor a cero"); val=Integer.parseInt(in.readLine()); }while(val<1); creaSb(aux,val,g);}}

86

Algoritmos y Estructuras de Datos

}while(op!=2); } boolean recoprin(String g){ boolean band=false; grafin prev,sig; sig=cabeza; prev=sig; while(sig!=null&&!g.equals(prev.setide())){ prev=sig; sig=sig.getliga(); } if(g.equals(prev.setide())) band=true; return band; } boolean recopron(String aux,String aux2,grafin g){ boolean band=true; grafini p,sig,prev; if(g.getsub()==null) if(aux.equals(aux2)) band=false; else{ p=g.getsub(); prev=p; sig=p; while(sig!=null&&!aux.equals(prev.setide())){ prev=sig; sig=sig.getliga(); } if(sig!=null) band=false; if(aux2.equals(aux)) band=false;} return band; } void creaSb(String s,int v,grafin m){ grafini p,prev,sig; if(m.getsub()==null){ p=new grafini(s,v,null); m.setsub(p);} else{ prev=m.getsub(); sig=prev; while(sig.getliga()!=null){ prev=sig; sig=sig.getliga(); } p=new grafini(s,v,prev.getliga()); prev.setliga(p); } } void mostrar(){ grafin prev,sig; prev=cabeza;

87

Algoritmos y Estructuras de Datos

sig=prev; while(sig!=null){ prev=sig; System.out.print(sig.setide()+"-->"); todo(sig.getsub()); System.out.print("\n"); sig=sig.getliga(); } } void todo(grafini m){ grafini prev,sig; prev=m; sig=prev; while(sig!=null){ prev=sig; System.out.print(" "+sig.getVal()+" "+sig.setide()+"-->"); sig=sig.getliga(); } } } d) Main para la representacin de grafos mediante multilistas import java.io.*; class grafiti{ public static void main(String args[])throws IOException{ grafit grafo=new grafit(); int op,op2; BufferedReader in=new BufferedReader(new InputStreamReader(System.in)); do{ System.out.println("Opciones\n1.Crear vertices\n2.Asignar ejes\n3.Mostrar grafo\n4.Salir\n5.Cotos minimos(para verla presione 2 veces esta opcion)"); op=Integer.parseInt(in.readLine()); switch(op){ case 1:do{ do{ System.out.println("1.Insertar nuevo nodo\n2.Salir"); op2=Integer.parseInt(in.readLine()); }while(op<1||op>2); if(op2==1){ System.out.println("Proporciona nombre del nodo"); grafo.creaLis(in.readLine()); } }while(op2!=2); break; case 2:grafo.creaSub(); break; case 3:grafo.mostrar(); break; case 4:System.out.println("Finalizando..."); break; case 5:grafo.floyd(); break; /* case 6:grafo.floyd2(); break;*/

88

Algoritmos y Estructuras de Datos

default:System.out.println("Opcion Invalida"); } }while(op!=4); } }

4. Programa que obtiene los caminos mnimos de un grafo mediante la implementacin del algoritmo de Floyd .
import java.io.*; import java.io.StreamTokenizer.*; class Grafo { File file; FileReader inFile; FileOutputStream fo; PrintStream out; String line; double time=0; final int INFINITO=999; double num=0; int ren=0; int col=0; int Matriz[][]=new int[70][70]; int P[][]=new int[70][70]; public static void main(String[] args) { if(args.length>=1) { try { Grafo g=new Grafo(args[0]); } catch(IOException e) {} } else System.out.println("De el archivo de entrada"); } public Grafo(String fileName) throws IOException { file=new File(fileName); inFile=new FileReader(file); Reader r=new BufferedReader(inFile); StreamTokenizer st=new StreamTokenizer(r); st.parseNumbers(); //lee numeros st.eolIsSignificant(true); //toma en cuenta \n int token=0;

89

Algoritmos y Estructuras de Datos

int aux=0; try { while(true) { token=st.nextToken(); if(token==st.TT_EOF) break; num=st.nval; if(token!=st.TT_EOL) //mientras no acabe el archivo { col=aux; Matriz[ren][col]=(new Double(num)).intValue(); aux++; } else { ren++; aux=0; } } col++; //el archivo debe terminar en \n file=new File("R_"+fileName); fo=new FileOutputStream(file); out=new PrintStream(fo,true); time=new Long(System.currentTimeMillis()).doubleValue(); out.println("Algoritmo Floyd"); out.println(); Despliega(Matriz,"La matriz del camino m s corto es: "); DespliegaCaminos(); time=(new Long(System.currentTimeMillis()).doubleValue()-time)/1000; out.println(); out.println("Tiempo de ejecucion= "+time+" segundos"); out.close(); } catch(EOFException e) {} }

public void Floyd() { for(int k=0; k<col; k++) for(int i=0; i<ren; i++) for(int j=0; j<col; j++) if((Matriz[i][k]+Matriz[k][j])<Matriz[i][j]) { P[i][j]=k; Matriz[i][j]=Matriz[i][k]+Matriz[k][j]; } }

90

Algoritmos y Estructuras de Datos

void camino(int inicio, int fin) { if(P[inicio][fin]!=0) { camino(inicio, P[inicio][fin]); out.print(getPath(P[inicio][fin])); camino(P[inicio][fin],fin); } } String getPath(int x) { char to=inc('A',x); return "->["+to+"]"; } char inc(char x, int max) { for(int i=0; i<max; i++) x++; return x; } void Despliega(int [][]M, String men) { out.println(men); for(int i=0; i<ren; i++) { for(int j=0; j<col; j++) if(M[i][j]<INFINITO) //se concidera oo a cualquiera >= INFINITO out.print("oo"+'\t'); //oo=infinito out.println(); } out.println("Renglones= "+ren); out.println("Columnas= "+col); } void DespliegaCaminos() { out.println(); out.println("Los cminos mas cortos son: "); char A='A'; //se denotan los nodos por letras iniciando en A char X=A; char Y=A; for(int i=0; i<ren; i++) { for(int j=0; j<col; j++) { if(i!=j) { out.print("El camino mas corto del nodo ("+X+") al nodo ("+Y+") es: "); out.print("["+X+"]"); camino(i,j); out.print("->["+Y+"]"); out.print(" y tiene un costo de: "+Matriz[i][j]);

91

Algoritmos y Estructuras de Datos

out.println(); } Y++; } X++; Y=A; } } }

5. Programa de un grafo no dirigido utilizando el algoritmo de Dijkstra para caminos minimos

import java.util.*; public class Grafo_Nodirigido { Vector vertices=new Vector(); int[][] mat_ady; int[][] mat_costos; int[] costos_minimos; int[] p; Vector s=new Vector(); public int tam_matriz(String[][] datos) { Integer num; for(int i=0;i<datos.length;i++) { for(int j=0;j<datos[i].length-1;j++) { num=new Integer(Integer.parseInt(datos[i][j])); if(vertices.indexOf(num)==-1) vertices.addElement(num); } } vertices.trimToSize(); return vertices.capacity(); } public void crea_mat_ady(int tam,String[][] datos) { mat_ady=new int[tam][tam]; for(int k=0;k<tam;k++) for(int l=0;l<tam;l++) mat_ady[k][l]=0;

92

Algoritmos y Estructuras de Datos

for(int i=0;i<datos.length;i++) mat_ady[Integer.parseInt(datos[i][0])][Integer.parseInt(datos[i][1])]=1; } public void crea_mat_costos(int tam,String[][] datos) { mat_costos=new int[tam][tam]; for(int k=0;k<tam;k++) for(int l=0;l<tam;l++) mat_costos[k][l]=10000; for(int i=0;i<datos.length;i++) mat_costos[Integer.parseInt(datos[i][0])][Integer.parseInt(datos[i][1])]= Integer.parseInt(datos[i][2]); } public void imprime_matriz(int[][] mat) { for(int i=0;i<mat.length;i++) { for(int j=0;j<mat.length;j++) System.out.print("\t"+mat[i][j]); System.out.println(" "); } //System.out.println("\n"); } public int primero(int v) { int v_ady=-1; for(int i=0;i<mat_ady.length;i++) { if(mat_ady[v][i]==1) { v_ady=i; break; } } return v_ady; } public int siguiente(int v, int i) { int v_sig=-1; for(int j=i+1;j<mat_ady.length;j++) { if(mat_ady[v][j]==1) { v_sig=j; break;

93

Algoritmos y Estructuras de Datos

} } return v_sig; } public { void encuentra_vertices_adyacentes(int v) int i=primero(v); System.out.print("\"Los Nodos adyacentes a "+v+" son\" : "); do{ System.out.print("\t"+i+""); i=siguiente(v,i); }while(i!=-1); System.out.print("\n"); } public void dijkstra() { costos_minimos=new int[mat_costos.length]; p=new int[mat_costos.length]; int min,ind,w; s.addElement(new Integer(0)); vertices.removeElementAt(0); vertices.trimToSize(); for(int i=1;i<mat_costos.length;i++) costos_minimos[i]=mat_costos[0][i]; for(int m=1;m<p.length;m++) p[m]=0; for(int j=0;j<mat_costos.length-1;j++) { ind=0; min=costos_minimos[((Integer)vertices.elementAt(0)).intValue()]; w=((Integer)vertices.elementAt(0)).intValue(); for(int k=1;k<vertices.size();k++) { if(min>costos_minimos[((Integer)vertices.elementAt(k)).intValue()]) { min=costos_minimos[((Integer)vertices.elementAt(k)).intValue()]; ind=k; w=((Integer)vertices.elementAt(k)).intValue(); } } vertices.removeElementAt(ind); vertices.trimToSize(); s.addElement(new Integer(w)); s.trimToSize();

94

Algoritmos y Estructuras de Datos

for(int l=0;l<vertices.size();l++) { if(costos_minimos[((Integer)vertices.elementAt(l)).intValue()]>costos_min imos[w]+mat_costos[w][((Integer)vertices.elementAt(l)).intValue()]) { costos_minimos[((Integer)vertices.elementAt(l)).intValue()]=costos_ minimos[w]+mat_costos[w][((Integer)vertices.elementAt(l)).intValue()]; p[((Integer)vertices.elementAt(l)).intValue()]=w; } } } }

public void recorre(int destino) { Vector recorrido=new Vector(); System.out.print("\"Camino mas corto entre el nodo 0 y el nodo "+destino+"\": "); if((destino!=0)&&(costos_minimos[destino]!=10000)) { do{ recorrido.insertElementAt(new Integer(destino),0); destino=p[destino]; }while(destino!=0); recorrido.insertElementAt(new Integer(0),0); for(int t=0;t<recorrido.size();t++) System.out.print(recorrido.elementAt(t)+""); System.out.print("\n"); }else System.out.println("****"); }

public static void main(String[] args) { String[][] datos; int i; Grafo_Nodirigido nografo=new Grafo_Nodirigido(); try{ Lee_Grafo archgrafo=new Lee_Grafo(args[0]); datos=archgrafo.lee_arch(); if(archgrafo.formato_valido()) { int n=nografo.tam_matriz(datos); nografo.crea_mat_ady(n,datos); nografo.crea_mat_costos(n,datos);

95

Algoritmos y Estructuras de Datos

System.out.println("\"Esta es la Matriz de Adyacencia de nuestro Grafo\": "); nografo.imprime_matriz(nografo.mat_ady); System.out.println("\"Esta es la Matriz de Costos de nuestro Grafo\": "); nografo.imprime_matriz(nografo.mat_costos); for(i=0;i<n;i++) nografo.encuentra_vertices_adyacentes(i); nografo.dijkstra(); System.out.print("\"Acontinuacion presentamos las distancias mas cortas\"\n"); for(int v=1;v<nografo.costos_minimos.length;v++) System.out.print("\t\t\tDel nodo 0 a el nodo"+v+": "+nografo.costos_minimos[v]+"\n");

for(i=0;i<n;i++) nografo.recorre(i); } else System.out.println("ERROR: El archivono contiene un formato correcto"); }catch(Exception e){ /*System.err.println("Falta nombre del archivo correcto")*/;} } }

* Clase para implementar programas de grafos

import java.io.*; import java.util.*; public class Lee_Grafo { public String[][] datos; String nomarch; boolean correcto; public Lee_Grafo(String nomarch) { this.nomarch=nomarch; this.correcto=true; this.datos=null; }

96

Algoritmos y Estructuras de Datos

public int cuenta_lineas() { FileReader fich; int x=0; try{ fich=new FileReader(nomarch); } catch(Exception e){System.err.println(e);return 1;} BufferedReader fichGrafo=new BufferedReader(fich); try{ while(fichGrafo.readLine()!=null) x++; }catch(Exception e){ System.err.println("Error: "+e);} return x; } public boolean formato_valido() { if(correcto) return true; else return false; } public String[][] lee_arch() { FileReader fich; try{ fich=new FileReader(nomarch); }catch(Exception e){ System.err.println(e);return datos;} BufferedReader fichGrafo=new BufferedReader(fich); try{ String linea; int x=-1; //System.out.println(cuenta_lineas()); datos=new String[cuenta_lineas()][3]; while((linea=fichGrafo.readLine())!=null) { x++; StringTokenizer tokens=new StringTokenizer(linea); try{ if(tokens.countTokens()!=3) { correcto=false; throw new Exception();

97

Algoritmos y Estructuras de Datos

} datos[x][0]=tokens.nextToken(); datos[x][1]=tokens.nextToken(); datos[x][2]=tokens.nextToken(); }catch(Exception e){ System.err.println("ERROR (linea "+x+"):"+linea);} } }catch(Exception e){ System.err.println("Error :"+e);} return datos; } }

98

Algoritmos y Estructuras de Datos

4. rboles
Un rbol es una estructura jerrquica, organizada y dinmica aplicada sobre una coleccin de objetos llamados nodos. Jerrquica porque los componentes estn a distinto nivel. Organizada porque importa la forma en que este dispuesto el contenido. Dinmica porque su forma, tamao y contenido pueden variar durante la ejecucin. Los rboles genealgicos y los organigramas son ejemplos comunes de rboles. Entre otras cosas, los rboles son tiles para analizar circuitos elctricos, para representar la estructura de frmulas matemticas, para organizar informacin en una base de datos, para representar el sistema de archivos y para analizar la estructura sintctica de un programa fuente en los compiladores. Existen diferentes formas de representacin de un rbol, entre las ms comunes se tienen las siguientes: Mediante crculos y flechas

a b e
Mediante parntesis anidados: ( a ( b (e,f), c, d )) Mediante notacin decimal de Dewey 1a, 1.1b, 1.1.1e, 1.1.2f, 1.2c, 1.3d Indentado, mediante nodos. Un buen ejemplo, es la forma de representar grficamente las carpetas (directorios) de un sistema de archivos. En este caso, una carpeta es un nodo padre de los archivos y subcarpetas contenidas en el.

c f

99

Algoritmos y Estructuras de Datos

La forma de representacin ms fcil, comn y que utilizaremos a lo largo de estas notas es la representacin mediante crculos y flechas.

IV.1 Definicin y conceptos bsicos


Definicin
Formalmente, un rbol se puede definir recursvamente como sigue [ ]: 1. Un solo nodo es, por s mismo, un rbol. Ese nodo es tambin la raz de dicho rbol. 2. Supngase que r es un nodo y que A1, A2, ...An son rboles con races r1, r2, ...rn, respectivamente. Se puede construir un nuevo rbol diciendo que r se constituya en el padre de los nodos r1, r2, ...rn. Por lo que, en dicho rbol, r ser ahora la raz y A1, A2, ...An sern los subrboles de r. Los nodos r1, r2, ...rn sern ahora tambin hijos del nodo r (ver Figura 1) Algunas veces se incluye entre los rboles el rbol nulo o vaco. El cual es un rbol sin nodos que se representa mediante la letra .

100

Algoritmos y Estructuras de Datos

A1

A2

An

Figura 1 Ejemplo de un rbol Generalmente, se crea una relacin o parentesco entre los nodos de un rbol que impone una estructura jerrquica y que da lugar a trminos como padre, hijo, hermano, antecesor, sucesor, etc. Se dice que la raz de cada subrbol Ak es un hijo de r y que r es el padre de cada raz de los subrboles. En principio cualquier nodo del rbol puede tener un nmero arbitrario de nodos hijos, a esto se le conoce como un rbol general. La Figura 2 muestra un ejemplo de esto. Si se limita el nmero de nodos hijos para cada nodo del rbol, digamos a un nmero n > 2 (llamado la aridad del rbol), entonces el rbol de aridad n es llamado n-ario.

M H I J

K
Figura 2 Arbol General El nodo A es la raz (padre). Los hijos de A son B,C, D, E Los nodos F,G, M son hermanos e hijos de B A es abuelo de H K y L son hijos de H y nietos de A

101

Algoritmos y Estructuras de Datos

Con estas consideraciones podemos definir las siguientes caractersticas y propiedades de los rboles. Algunos de los siguientes conceptos; si embargo, no son uniformes en toda la literatura referente a la teora de rboles.
Conceptos bsicos
a) Si hay un camino de A hasta B, se dice que A es antecesor de B, y que B es sucesor de A. b) Padre es el antecesor inmediato de un nodo c) Hijo, cualquiera de sus descendientes inmediatos. d) Antepasado de un nodo, es cualquier antecesor de dicho nodo. e) Descendiente de un nodo, es cualquier sucesor de dicho nodo. f) Hermano de un nodo, es otro nodo con el mismo padre. g) Raz es el nodo que no tiene ningn predecesor. h) Hoja (o nodo terminal) es el nodo que no tiene sucesores. i) Los nodos que tienen predecesor y sucesor se llaman nodos interiores. j) Rama es cualquier camino del rbol. k) Bosque es un conjunto de rboles desconectados. l) Grado de un nodo, es el nmero de flechas que salen de ese nodo. El nmero de flechas que entran siempre es uno. m) Grado de un rbol, es el mayor grado que puede hallarse en sus nodos. n) Nivel o profundidad de un nodo, es la longitud del camino desde la raz hasta ese nodo. El nivel puede definirse como 1 para la raz y nivel(predecesor)+1 para los dems nodos. o) Generacin, es un conjunto de nodos con la misma profundidad. p) Altura de un nodo, es la longitud del camino desde ese nodo hasta la hoja ms alejada (la altura de una hoja es 0 y la de un rbol vaco se considera -1). q) Altura de un rbol, es la altura desde la raz. Esto es, es el mximo de los niveles de todos los nodos del rbol. r) Un camino de un nodo n1 a otro nk, se define como la secuencia de nodos n1, n2, ... nk tal que ni es padre de ni+1 para 1 i < k. s) Longitud del camino entre 2 nodos: Es el nmero de arcos que hay entre ellos. Se supone que la longitud del camino a la raz es 1, por lo que su hijo tiene longitud 2. t) Longitud del camino interno (LCI) de un rbol, es la suma de las longitudes a todos los nodos. Se supone que la longitud de la raz es uno.

LCI = Ni * Li
i =1

donde Ni=nmero de nodos en el nivel i, Li=longitud hasta generacin i y h es la altura u) Media de longitud del camino interno (MLCI) de un rbol, es el promedio de accesos al rbol para llegar a cualquier nodo del rbol y se calcula

MLCI = LCI
donde n es el nmero de nodos del rbol.

102

Algoritmos y Estructuras de Datos

Ejemplo: Utilizando el rbol de la Figura 2 tenemos: a) b) c) d) e) f) g) h) i) l) A es antecesor de F y F es sucesor de A B es el padre de G y H es el padre de K I y J son hijos de E y K y L son hijos de H. A , D y H son antepasados de K y L Los descendientes de D son H, K y L I y J son hermanos. B,C, D, y E son tambin hermanos. El nodo A es la raz F,G, K,L, I y J son hojas del rbol B, D, H, E son nodos interiores El grado de A es 4 El grado de B es 2 El grado de C es 0 El grado del rbol es 4 El nivel de A es 1 El nivel de B es 2 El nivel de H es 3 El nivel de K es 4 F,G, H, I, y J son de la generacin 3 La altura del nodo D es 2 La altura del nodo H es 1 La altura del nodo G es 0 La altura del rbol es 4 El camino de A a K es nico y lo forman los nodos A-D-H-K El nodo B tiene longitud de camino 2 desde A El nodo I tiene longitud de camino 3 desde A El nodo K tiene longitud de camino 4 desde A

m) n)

o) p)

q) r) s)

Orden de los nodos


Generalmente los rboles de un nodo se ordenan de izquierda a derecha. Por ejemplo, los rboles de la Figura 3 son distintos porque los dos hijos del nodo x aparecen en diferente orden en los dos rboles. Si no se toma en cuenta el orden de los nodos hijos, entonces se habla de un rbol no ordenado.

Figura 3 rboles ordenados distintos El orden de izquierda a derecha de los hermanos se puede extender para comparar dos nodos cualesquiera entre los cuales no exista la relacin antecesor-descendiente. La

103

Algoritmos y Estructuras de Datos

regla que se aplica es que si y y z son hermanos y y est a la izquierda de z, entonces todos los descendientes de y estarn a la izquierda de todos los descendientes de z. Esto es, y es menor que z.

IV.2 rboles Binarios


Un rbol binario es un rbol de grado 2, en el que todo nodo del rbol tiene un subrbol binario izquierdo y derecho asociados (ver la Figura 4 )
Raz

Subrbol Izquierdo

Subrbol Derecho

Figura 4 rbol binario genrico

Terminologa de Arboles Binarios


Arbol Binario Completo o Lleno: Es un rbol binario en el que todos sus nodos, excepto las hojas, tienen siempre dos hijos ( el subrbol izquierdo y el derecho) no nulos. El nmero de nodos de un rbol completo se calcula por la frmula: Nmero de nodos = 2h-1 (donde h es la altura) Adems, siendo 1 el nivel de la raiz, el nmero mximo de nodos en un nivel k ser 2k1. Arbol Binario Completo de Altura o Profundidad H: Es un rbol Binario Completo en donde todas las hojas estn en el nivel H. Esta es una de las pocas estructuras de rbol que se pueden representar eficientemente usando arreglos.

Arbol Binario de Expresin (ABE):Una de las aplicaciones de rboles binarios son los llamados rboles de expresin. Una expresin es una secuencia de componentes lxicos (tokens), que siguen reglas preescritas. Un token puede ser un operador o un operando. Las propiedades de un rbol de expresin son las siguientes: 1. Cada hoja es un operando 2. El nodo raz y los nodos internos son operadores 3. Los subrboles son sub-expresiones en las que el nodo raz es un operador La Figura 5 muestra un ejemplo de un rbol de expresin de la expresin (a+b) * (c-d)

104

Algoritmos y Estructuras de Datos

* +
a b c

Figura 5 Ejemplo de rbol de expresin Arboles Binarios de bsqueda:Un rbol binario de bsqueda es un rbol en el que todo

nodo existente tiene un solo elemento y cumple lo siguiente:


todas las claves del subrbol izquierdo son menores que la raz, todas las claves del subrbol derecho son mayores que la raz, los subrboles izquierdo y derecho son tambin rboles de bsqueda. Los nodos insertados en rboles de bsqueda binarios se insertan como hojas. Hacerlo de otro modo no solo no mejorara la eficiencia buscada, sino que adems habra que reajustar el rbol tras cada insercin. La Figura 6 muestra un ejemplo de un rbol de bsqueda de nmero ordenados.

7 5 10

11

Figura 6 Ejemplo de rbol binario de bsqueda Por ejemplo, al insertar la clave 8, el rbol de la Figura 6, quedara de la siguiente forma:

7 5 10

11

8
Figura 7 Insercin en un rbol binario de bsqueda

105

Algoritmos y Estructuras de Datos

Conversin de rboles generales como binarios

Como veremos ms adelante, la manipulacin y representacin de rboles binarios es mucho ms eficiente que la de rboles generales, por lo que es muy til conocer una forma de convertir un rbol general en un rbol binario. Esto se puede lograr a travs de los siguientes pasos:
1. 2. 3. 4. Enlazar horizontalmente todos los hermanos. Para cada nodo, enlazar verticalmente el nodo padre con el nodo hijo que est ms a la izquierda, eliminado el vnculo del padre con el resto de sus hijos. Todos los enlaces verticales son hijos izquierdos del nuevo rbol binario. Todos los enlaces horizontales (hermanos) son hijos derechos.

a b e i j f k c g d h i e j b f k

a c g d h i e j b f k

a c g d h

a b e i j k f g h c d

Figura 8 Ejemplo de la representacin de rboles Generales como rbol Binario

Conversin de Bosques como Arboles Binarios


1. 2. 3. 4. 5. Enlazar horizontalmente las races de los distintos rboles generales. Para todo nodo del bosque enlazar verticalmente el nodo padre con el nodo hijo que est ms a la izquierda, eliminando el vnculo del padre con el resto de sus hijos. Enlazar horizontalmente los hermanos. Todos los enlaces verticales son hijos izquierdos del nuevo rbol binario. Todos los enlaces horizontales son hijos derechos.

106

Algoritmos y Estructuras de Datos

a d i j e k

d f g

c h

Figura 9 Ejemplo de la conversin de un bosque con rbol binario

Recorridos

Muchas de las operaciones del TDA -Arbol Binario implican recorrer o visitar cada uno de los nodos del rbol, ya sea para insertar, eliminar, visitar o buscar un elemento de una forma eficiente. Existen en general cuatro formas de hacerlo, tres de naturaleza recursiva y uno ms de naturaleza iterativa.
a. Recorrido en PreOrden (u orden previo).- Iniciando en la raz, primero se visita sta, luego se hace un recorrido en PreOrden del subrbol Izquierdo y luego en el subrbol derecho, tambin en PreOrden. b. Recorrido en InOrden (orden simtrico).- Iniciando en la raz, primero se efecta un recorrido en InOrden en el subrbol izquierdo, luego se visita la raz, y luego se visita el subrbol derecho tambin en InOrden. c. Recorrido en PostOrden (u orden posterior).- Iniciando en la raz, primero se visita en PostOrden el subrbol izquierdo, luego el subrbol derecho, tambin en PostOrden, y por ltimo se visita la raz. d. Recorrido por niveles.- Iniciando en la raz, primero se visita la raz, y luego se visitan los elementos del segundo nivel de izquierda a derecha, seguidos por los del nivel 3 en el mismo orden, y as sucesivamente hasta terminar de visitar todos los elementos. Como ejemplo consideremos el rbol de la Figura 10.
d

PreOrden: daebcp InOrden: aedcbp PostOrden: eacpdb Niveles: dabecp

Figura 10 Ejemplo 1 de recorridos

107

Algoritmos y Estructuras de Datos

Otro ejemplo ms se muestra en la Figura 11.

PreOrden : a b e i j k f c d g h InOrden: i e j k b f a c g d h Postorden : i j k e f b c g h d a

Figura 11 Ejemplo 2 de recorridos El siguiente ejemplo ( Figura 12) muestra el recorrido en un rbol de expresin.

PreOrden : *+ab-cd (expresin Prefija) InOrden: a+b*c-d (expresin Infija) Postorden : ab+cd-* (expresin Postfija)

+
a b c

Figura 12 Recorrido en un rbol de expresin

As, el algoritmo para el recorrido en PreOrden sera de la siguiente forma: PreOrden (NodoB Nodo) { If (Nodo != null) { Imprime(Nodo.info); PreOrden(Nodo.Izq); PreOrden(Nodo.Der); } }

//Visita Raiz

108

Algoritmos y Estructuras de Datos

Los algoritmos para InOrden y PostOrden son semejantes.

Especificacin del TDA - Arbol Binario


Los elementos de un rbol binario son los Nodos Binarios. Se les llama as puesto que adems de guardar informacin asociada con el nodo, tienen informacin sobre sus sucesores en la estructura jerrquica del rbol. Por esto, es conveniente especificar e implementar primero el TDA-Nodo Binario, antes de especificar el TDA - Arbol Binario.

Implementacin del TDA-Nodo Binario (Cdigo en Java)


public class NodoBin extends Nodo { protected NodoBin izq; protected NodoBin der;
public NodoBin() { super(); izq = null; der = null; } public NodoBin(String str) { super(str); izq = null; der = null; } public NodoBin(Nodo x) { super(x); izq = null; der = null; } public void setIzq(NodoBin x) { izq = x; } public void setDer(NodoBin x) { der = x; } public NodoBin getIzq() { return izq; } public NodoBin getDer() { return der; } public static void main(String [] args) {

109

Algoritmos y Estructuras de Datos

NodoBin e1 = new NodoBin("6678-VB"); NodoBin e2 = new NodoBin("56TY-UYA"); NodoBin e3 = new NodoBin("12345"); e1.show(); e2.show(); e3.show(); } }

Para identificar el estado del TDA - Arbol Binario se debe considerar lo siguiente: Peso o Nmero de elementos del rbol Grado del rbol Altura del rbol Raz del rbol Elemento corriente o actual del rbol Elementos del rbol Adems, se debe asumir que: a. El rbol binario est formado por elementos del tipo TDA-Nodo Binario y no hay nodos repetidos en el rbol. b. El rbol binario puede tener cero o ms nodos, si tiene cero, se trata de una rbol binario vaco. c. De existir el primer elemento de un rbol binario, este es su nodo raz. d. El nodo sobre el que se efectan algunas operaciones es el nodo actual o nodo corriente del rbol binario. e. Todos los nodos excepto la raz, tienen un nodo predecesor o nodo padre, as como dos sucesores: el subrbol izquierdo y derecho, respectivamente. f. Los nodos hojas tienen subrboles binarios izquierdo y derecho vacos.

Operaciones del TDA-Arbol Binario


Las posibles operaciones con el TDA - Arbol Binario seran: CrearArbolBinario insertar borrar getraiz getSubIzq getSubDer getPeso getAltura getGrado esVacio esCompleto showPreOrden showInOrden showPostOrden showNiveles

110

Algoritmos y Estructuras de Datos

Implantacin del TDA-Arbol Binario


Existen diversas formas de implantar un rbol binario entre estas estn: 1. rboles Enlazados a) b) c) d) e) rboles encadenados. rboles con encadenamiento al padre. rboles enhebrados por la derecha. rboles enhebrados por la izquierda. rboles totalmente enhebrados.

2. Arreglos lineales a) Arreglos Estticos b) Arreglos Dinmicos

Implementacin del TDA-Arbol Binario con Arboles Enlazados


rboles Encadenados Es la implementacin ms sencilla y una de las ms usadas. La idea es la de representar un rbol binario a travs de nodos encadenados (ver Figura 13 )

Figura 13 Representacin de un rbol utilizando nodos encadenados

111

Algoritmos y Estructuras de Datos

Implementacin del TDA-Arbol Binario con Arboles con Encadenamiento al Padre Es una variante de la implementacin de rbol encadenado, consiste en que cada elemento del rbol mantiene una referencia a su padre. Esto permite ascender con facilidad por la estructura jerrquica en busca de los antecesores. (ver Figura 14)

Figura 14 Representacin del TDA-Arbol Binario con Encadenamiento al Padre

En los rboles enhebrados todas las referencias nulas, son reemplazadas por referencias hacia sus predecesores y/o sucesores, segn su recorrido en InOrden en el rbol Binario. A estas ligas en un rbol enhebrado se les conoce como lianas. La idea de esta implementacin consiste en mantener un encadenamiento adicional, que permita a algunas operaciones del TDA-rbol binario moverse con mayor facilidad al interior del rbol, lo que va a permitir realizar recorridos, inserciones y eliminaciones no recursivas. Para distinguir si se trata de una referencia a un hijo o a un sucesor/predecesor en InOrden, el TDA-NodoBin requiere de un campo adicional, que le indique si se trata de una rama o de una liana.

Ejemplo de Implementacin del TDA-Arbol Binario con Arboles Encadenados (Cdigo en Java)
public class ArbBin { protected NodoBin raiz; public ArbBin() { raiz = null; } public ArbBin(NodoBin n) {

112

Algoritmos y Estructuras de Datos

raiz = n; } public void insertar(NodoBin r, NodoBin izq, NodoBin der) { r.setIzq(izq); r.setDer(der); } public NodoBin getRaiz() { return raiz; } public ArbBin getSubIzq() { return new ArbBin(raiz.getIzq()); } public ArbBin getSubDer() { return new ArbBin(raiz.getDer()); } public boolean esVacio() { if(raiz == null) { return true; } else { return false; } } public static void showPreOrden(ArbBin a) { if(a.esVacio() == false) { a.getRaiz().show(); showPreOrden(a.getSubIzq()); showPreOrden(a.getSubDer()); } } public static void showInOrden(ArbBin a) { if(a.esVacio() == false) { showInOrden(a.getSubIzq()); a.getRaiz().show(); showInOrden(a.getSubDer()); } } public static void showPostOrden(ArbBin a) { if(a.esVacio() == false) { showPostOrden(a.getSubIzq()); showPostOrden(a.getSubDer()); a.getRaiz().show(); } } public static void main(String [] args) { NodoBin n1 = new NodoBin("1"); NodoBin n2 = new NodoBin("2");

113

Algoritmos y Estructuras de Datos

NodoBin n3 = new NodoBin("3"); NodoBin n4 = new NodoBin("4"); NodoBin n5 = new NodoBin("5"); ArbBin a = new ArbBin(n1); a.insertar(n1,n2,n3); a.insertar(n3,n4,n5); showPreOrden(a); showInOrden(a); showPostOrden(a); } }

Implementacin del TDA - Arbol Binario con arreglos dinmicos


Es posible utilizar una estructura lineal para representar un rbol binario, aunque es de esperarse que no sea muy eficiente. Para el caso de un rbol binario de altura H, ste se puede representar en un arreglo lineal de 2H-1 elementos: a={x0, x1, , xn}, n < 2k-1.

1. Primero debe completar el arbol de tal forma que este se convierta en un rbol binario completo de altura H. Todos los elementos nulos pueden representarse con un smbolo especial. 2. Colocar, secuencialmente en el arreglo, todos los elementos del rbol binario completo, segn su recorrido por niveles. No olvide colocar tambin los elementos nulos. 3. De esta forma, el k-simo elemento del arreglo tiene a sus hijos izquierdo y derecho en las posiciones 2k+1 y 2k+2, respectivamente. A continuacin, en la Figura 15, se muestra la implantacin de un rbol binario urilizando arreglos.

Figura 15 Representacin de un rbol con arreglos

IV.3 rboles AVL


Un arbol AVL (Adelson-Velskii & Landis) es un rbol binario de bsqueda con una condicin de equilibrio. Una buena condicin de equilibrio es pedir que, para todo nodo en

114

Algoritmos y Estructuras de Datos

el rbol, la altura 1 de sus subrboles izquierdo y derecho difiera como mximo en 1. En la Figura 16, se muestran dos ejemplos de rboles AVL vlidos.
1 2 2 4 1 3 3 4 7 6 8

Figura 16 Ejemplos de rboles AVL

A continuacin, se muestran dos ejemplos de rboles que no cumplen la condicin de equilibrio establecida y por lo tanto no son rboles AVL.
5 2 4 3 2 6 1 7 3 8 7 4 6 8

Figura 17 Ejemplo de rboles que no son AVL

Insercin de un nodo
Podemos insertar un nodo utilizando el algoritmo de insercin de un nodo en un rbol binario de bsqueda, comparando la clave del nuevo elemento con la raz e insertando el nodo en el subrbol izquierdo o derecho segn corresponda. Sin embargo, la razn por la que la insercin en un rbol AVL es potencialmente difcil es que al insertar un nodo se puede violar su condicin de equilibrio. En la siguiente figura se muestra lo que pasara al insertar la clave 6
5

Condicin de equilibrio AVL, destruida en el nodo 8 (Las alturas de sus subrboles izquierdo y derecho difieren en ms de 1)

2 1 3 4 6 7

Nuevo nodo

En este caso se tiene que restaurar la condicin de equilibrio antes de terminar el proceso de insercin. Para saber cuando se ha perdido la propiedad AVL es
1

Altura de un nodo es la longitud del nodo a la hoja ms alejada. Altura de una hoja es cero, altura de rbol vaco es 1

115

Algoritmos y Estructuras de Datos

necesario conocer el factor de equilibrio de cada nodo afectado por la insercin o borrado. Esto puede hacerse utilizando alguna de las siguientes formas:
Calculando la diferencia de altura de los subrboles para cada nodo. Almacenando la altura de cada nodo en un campo extra del propio nodo. Almacenando la diferencia de altura de cada nodo en un campo extra del propio nodo. Este campo se llama factor de equilibrio (FE) y es igual a (altura subrbol derecho)(altura subrbol izquierdo). La ltima opcin es la ms eficiente. A cambio exige que: 1. Al insertar un nuevo nodo, este campo tomar el valor cero ya que siempre se inserta por las hojas. 2. Despus de cada insercin hay que revisar el FE de los nodos involucrados, que sern los que componen el camino desde la raz del rbol hasta el padre del nodo insertado. 3. Decimos que el nodo esta desequilibrado a la izquierda o derecha cuando su FE es, respectivamente, negativo o positivo. Para corregir el FE de un nodo, debemos realizar sobre l una rotacin simple o doble segn corresponda.

Restauracin de propiedad AVL


Como mencionamos anteriormente, al insertar un nuevo nodo (o al borrarlo) es posible que se destruya la condicin de equilibrio requerida por los rboles AVL. Para restaurar esta propiedad se puede hacer una modificacin al rbol conocida como rotacin. Bsicamente, existen dos tipos de rotaciones: 1. Rotacin sencilla (simple) 2. Rotacin doble No es obligatorio que la rotacin se haga en la raz del rbol, se puede hacer en cualquier nodo del rbol, ya que cualquier nodo es raz de algn subrbol y puede transformar cualquier rbol en otro.

Rotaciones simples
Sea el rbol binario de bsqueda

A B
T3 T1 T2

Que sabemos? 1. B < A 2. Todos los elementos de T1 son menores que B 3. Todos los elementos de T3 son mayores que A 4. Todos los elementos de T2 estn entre A y B

116

Algoritmos y Estructuras de Datos

B A
T1 T2 T3

Al realizar una rotacin simple a la derecha, cambia la estructura del rbol pero preserva la propiedad de rbol binario de bsqueda

As, el mtodo s sencillo para arreglar un rbol AVL, si la insercin (o borrado) causa que algn nodo pierda la propiedad de equilibrio es hacer una rotacin en ese nodo, bajo las siguientes consideraciones: Si est desequilibrado a la izquierda (FE < 1), y su hijo derecho tiene el mismo signo () hacemos rotacin sencilla izquierda. Si est desequilibrado a la derecha (FE > +1), y su hijo izquierdo tiene el mismo signo (+) hacemos rotacin sencilla derecha.

A continuacin se muestra grficamente las rotaciones sencillas a izquierda y derecha.

117

Algoritmos y Estructuras de Datos

Rotaciones dobles aciones


Rotacin doble de derecha a izquierda

C A
T1

B
T4

T2

T3

T1

T2

T3

T4

Rotaciones dobles Si est desequilibrado a la izquierda (FE < 1), y su hijo derecho tiene distinto signo (+) hacemos rotacin doble izquierda-derecha. Si est desequilibrado a la derecha (FE > +1), y su hijo izquierdo tiene distinto signo () hacemos rotacin doble derecha-izquierda.

118

Algoritmos y Estructuras de Datos

119

Algoritmos y Estructuras de Datos

Rotaciones para equilibrar un rbol AVL


B A
Rotacin derecha

A B
Rotacin izquierda

B A

T3 T2

T1 T2

T3 T2

T1

T3

T1

C B A B T4
Rotacin doble derecha Rotacin doble izquierda

C A B

T4

T1 T2

T1 T2

T3

T1

T2

T3

T4

T3

Ntense las equivalencias en: la rotacin sencilla (ej: derecha) ( (T1 A T2) B T3) = (T1 A (T2 B T3) ) y la rotacin doble (ej: izquierda) ( T1 A ((T2 B T3) C T4) = (T1 A T2) B (T3 C T4) )

Cuando el nuevo nodo insertado es un valor intermedio entre el nodo que incumple la condicin de equilibrio y el hijo del nodo desequilibrado por el que hemos pasado para insertar el nuevo nodo, se usara rotacin doble para equilibrar el rbol. En los dems casos usaremos rotacin simple.

ALGORITMO DE ROTACIN
1. Iniciamos en el nodo insertado y subir en el rbol. 2. Actualizar la informacin de Factor de Equilibrio en cada nodo del camino. 3. Si se encuentra un nodo desequilibrado aplicar una rotacin ajustando su equilibrio. a. Si est desequilibrado a la izquierda (FE<-1) y si su hijo derecho tiene el mismo signo (-) hacemos rotacin sencilla a la derecha. b. Si est desequilibrado a la derecha (FE>+1)y su hijo izquierdo tiene el mismo signo (+) hacemos rotacin sencilla a la izquierda. c. Si est desequilibrado a la izquierda (FE<-1) y su hijo izquierdo tiene distinto signo (+)hacemos rotacin doble izquierda-derecha. 4. Si llegamos a la raz, terminamos.

120

Algoritmos y Estructuras de Datos

A continuacin se muestran los algoritmos para rotar a la izquierda y a la derecha.

Implementacin del algoritmo para rotar (Cdigo en Java)


public void RotarIzquierda(NodoBin x) { NodoBin y; y = x.der; x.der = y.izq; y.izq = x; x = y; }

public void RotarDerecha(NodoBin x) { NodoBin y; y = x.izq; x.izq = y.der; y.der = x; x = y; }

121

Algoritmos y Estructuras de Datos

5. APLICACIONES DE LOS TIPOS ABSTRACTOS DE DATOS 5.1 Almacenamiento disperso (hash)

El mtodo de direccionamiento de acceso aleatorio, conocido como HASH (dispersin), es una til e ingeniosa forma de convertir la clave de un registro (campo que lo identifica) en un nmero pseudo-aleatorio, que sirve para determinar la direccin fsica donde se almacena dicho registro. La figura siguiente ilustra tal idea. Espacio Registro clave Algoritmo que convierte la clave en un nmero Fsico de Direcciones Esto recuerda claramente la definicin de funcin: CONJUNTO A funcin h CONJUNTO B

O bien como: h: A a B h(a)

As tenemos que h debe ser una funcin, tal como se define en matemticas. Ahora bien, todo parece indicar que el problema principal del hash consiste en la construccin de la funcin h. Qu resultados se esperan con tal funcin h ? a) Que distribuya de la manera ms uniforme posible las claves en todo el espacio de direccionamiento. b) Que el valor obtenido, al aplicar h a alguna clave, no desborde el espacio de direccionamiento. Para el inciso (b) la solucin es trivial si aplicamos al valor obtenido la funcin mdulo n, donde n sera el tamao del espacio de direcciones. As tendramos h mod n.

122

Algoritmos y Estructuras de Datos

Llama la atencin que este mtodo requiere un espacio de direcciones continuo (almacenamiento secuencial) aunque su manejo parezca ms de tipo dinmico. Sobre el inciso (a), no existe mtodo alguno que nos asegure que la funcin h distribuir de manera totalmente uniforme las claves sobre el espacio de direcciones. As puede llegar un momento en que: h(a) = h(b) con a <> b. Cuando esto sucede se dice que existe una colisin : "a dos o ms registros con clave diferente corresponde una misma direccin en el espacio de direcciones". Qu hacer en estos casos ? Existen varios mtodos para resolver las colisiones. La eleccin de un mtodo depende de la funcin h que se tenga implementada, la naturaleza del problema y la cantidad de memoria disponible. Antes de tratar los mtodos para resolver colisiones, hablemos sobre los mtodos para obtener la funcin h. La funcin h se puede clasificar en dos grupos: Numrica h Lgica La funcin h es numrica si para su implementacin se utilizan operaciones aritmticas (sumas, divisiones, etc.) y es lgica si se utilizan operaciones lgicas (and, or, etc.). Aunque en algunos casos la funcin h es hbrida, esto es, son combinacin de una o varias operaciones aritmticas con una o varias operaciones lgicas. A continuacin se presentan algunos de los mtodos ms comunes para desarrollar funciones hash.
Centro de los cuadrados

La clave se multiplica por s misma, se toman los dgitos centrales del cuadrado y se ajustan al espacio de direccionamiento. Ejemplo: Si la clave es 1722148 su cuadrado es 029634933904, la clave obtenida sera 3493 mod n.
Divisin

La clave se divide por un nmero aproximadamente igual al nmero de direcciones disponibles y el resto de la divisin es la direccin deseada. Por razones obvias como divisor se usa el nmero primo ms cercano al tamao del espacio de direcciones o uno que no incluya factores primos pequeos. Una de las razones por las cuales la divisin tiende a generar menos desbordes que los algoritmos generadores de nmeros aleatorios es que en la mayora de los casos las claves tienen valores consecutivos. 123

Algoritmos y Estructuras de Datos

Desplazamiento

En este mtodo los dgitos exteriores (en ambos extremos) de la clave se recorren hacia la derecha de tal forma que quedan trasladados en la medida de la longitud de la direccin, la suma de estos nmeros es la clave deseada. Ejemplo: 127207359 | | ----->1720 | 7359 <-------9079

Conversin de raz

Se escoge un nmero arbitrario (un primo) llamado raz el cual servir como nueva base, y en la serie de dgitos resultantes se suprimen los dgitos excedentes de mayor orden. Ejemplo: Adoptando la raz 11, la clave 172148 se transforma en 1 x 11 5 + 7 x 11 4 + 2 x 11 3 + 1 x 11 2 + 4 x 11 1 + 8 = 266373 y suprimiendo el 26 la clave restante es 6373.

Divisin polinmica

Cada dgito de la clave es coeficiente de un polinomio, as la clave 172148 se contempla como: X 5 +7X 4 +2X 3 +X 2 +4X+8 El polinomio as obtenido se divide por otro polinomio (fijo). Los coeficientes del resto forman la direccin deseada. Debe tomarse en cuenta que la transformacin ideal no es la que distribuye el conjunto de claves aleatoriamente, sino tan uniformemente como sea posible en el espacio de direcciones. Para el tratamiento de los desbordes existen las siguientes alternativas: a) La clave que provoca la colisin se almacena en un rea de datos aparte. b) La clave de desborde se almacena en el rea prima.

124

Algoritmos y Estructuras de Datos

Para el primer caso (ver figura) tenemos una tabla de desbordes la cual se puede manejar bajo cualquiera de los TADs conocidos: listas ligadas, almacenamiento secuencial, mediante otra funcin hash (rehash), un rbol, etc.

AREA PRIMA h(a)

AREA DE DESBORDE

colisin h(b)

Para el segundo caso (siguiente figura) se busca en la misma tabla espacio para almacenar la clave. Esta se puede realizar con bsqueda secuencial, por bloques, por referencias ligadas etc. h(a) colisin h(b)

Es deseable que la tabla se declare llena cuando se tiene ocupado el 97% de la tabla (factor de carga). De modo que cuando ocurra una colisin no se dificulte dar acomodo a la clave que lo ocasion.

5.2 TAD Tabla de Smbolos (TS)

Durante las etapas de compilacin se requieren varias operaciones relacionadas con los identificadores, sean de variables, procedimientos, u otros. Por ejemplo los siguientes conceptos tienen relacin con el tratamiento de los identificadores: 1. Referencias. Cuando se va a operar con alguna unidad lxica "nombre" a nivel semntico es necesario indicar con precisin cul de las unidades lxicas en particular se considera. As la etapa sintctica no slo conlleva al valor lxico sino adems un nmero de identificacin propio. 2. Alcance. La referencia a una variable, durante la traduccin dirigida por sintaxis y en la optimizacin, requiere del bloque donde se hizo la definicin. 125

Algoritmos y Estructuras de Datos

3. Tipos. Es un atributo de un identificador. Una de sus utilidades es elegir la operacin correcta, en la generacin de cdigo o bien para reservar reas de memoria correspondientes a los valores que tomarn las variables, en la optimizacin. En general es necesario manejar la informacin acerca de los identificadores utilizados en un programa. Podemos observar que cada identificador tiene asociados atributos, por tanto la estructura para manejar esta informacin debe ser una tabla asociativa, llamada Tabla de Smbolos : identificador atributos

Las operaciones bsicas en esta tabla son: a) b) c) d) e) Determinar si un identificador est en la tabla. Aadir un nuevo identificador a la tabla. Obtener informacin asociada a cierto identificador. Aadir informacin a un nombre determinado. Eliminar un identificador o un grupo de identificadores.

Existen muchas posibilidades para representar este tipo de TAD. En todos los casos la mejor seleccin se hace de acuerdo a las caractersticas del lenguaje de programacin particular que se desea implantar. Es comn emplear alguno de los TADs siguientes en una TS: 1) Arreglo secuencial de registros (nombre - atributo). 2) Listas autoorganizadas (lista ligada que redefine su cabeza con el ltimo elemento consultado). 3) Arbol binario balanceado. 4) Hash. Uno de los ejemplos ms completos del TAD Tabla de Smbolos es la estructura de datos para la tabla de ALGOL. Analizando esta tabla concretaremos las ideas anteriores. De acuerdo a la regla "la referencia a un identificador corresponde a la definicin dentro del nivel ms cercano de anidacin", debern considerarse dos tipos de identificadores durante la compilacin. Aquellos que estn en algn bloque actualmente activo y los que han dejado informacin para las subsecuentes etapas (bloques anteriores). Es por esto que en la tabla se forman dos secciones: nombres activos e inactivos. La TS de ALGOL utiliza los siguientes TADs: Tabla Hash para nombres Arreglos separados (nombre - informacin) 126

Algoritmos y Estructuras de Datos

Tabla de bloques activos e inactivos. Tabla de cadenas.

El funcionamiento de estos TADs es como sigue: para cada nombre que se va a analizar se obtiene el valor hash, las cadenas de equivalencia se encuentran en el arreglo de nombres almacenados de forma que el ltimo se encuentre al inicio (los primeros al final de la lista), para ello se verifica el nombre en la tabla de cadenas. El nombre puede aparecer en alguno de los bloques activos, finalmente si se encuentra el nombre podemos consultar su informacin en el arreglo separado. Para clarificar el funcionamiento de la TS. Supngase que en la siguiente estructura de bloques de un programa el anlisis se encuentra en la posicin indicada: BEGIN BEGIN BEGIN END END BEGIN <---END END \ B3 / \ > B4 / | > B2 | \ | | | > | | | /

B1

En estas condiciones se tendra una tabla de smbolos como la siguiente: arreglo separado informacin arreglo de nombres B1 B4 hash utilb endb tabla de bloques B1 B4

Inf. de nom

apunts. de nom util end ...

B2 B3

B4tc nom

utiltc

Tabla de Cadenas

127

Algoritmos y Estructuras de Datos

Las operaciones que se realizan en este TAD no son exclusivamente el ingreso y consulta de nombres, adems cada vez que se inicia o termina un bloque (begin, end) deben desalojarse reas de cada parte del TAD. El arreglo de nombres funciona como "acorden" para mantener las referencias a los nombres activos e inactivos, al igual que la tabla de bloques. Por su parte la tabla de cadenas es desalojada segn el apuntador de inicio de un bloque, cada vez que se finaliza (end).
Ejercicio Escriba los algoritmos para realizar los cambios en la TS de ALGOL cuando:

a) Llega un nuevo nombre b) Empieza un bloque c) Termina un bloque d) Hay una referencia a un nombre

5.3 Recoleccin de basura

Supngase que se desea construir un manejador de listas de propsito general, cuyos elementos pueden ser solicitados por otros programas (espacio libre). Existen dos mtodos para tal fin: un contador de referencias y un colector de basura. El primer esquema necesita un campo, por cada nodo, el cual contiene el nmero de apuntadores que referencan ese nodo, y cuando la cuenta es cero, el nodo est disponible. La tcnica de recoleccin de basura, requiere un campo, de un bit, por cada nodo. La idea bsica del algoritmo es dejar que el programa corra y utilice los nodos de memoria sin liberarlos, hasta que no existan ms nodos disponibles, entonces se efecta un "reciclaje" de los nodos, recuperando, aquellos que se encuentran disponibles (bit de marca), una vez terminada la recoleccin el programa continua normalmente. Los dos mtodos anteriores tienen desventajas. El contador de referencias no permite liberar todos los nodos que estn disponibles, ya que para el caso de listas lineales los nodos se liberan, pero no en el caso de listas recursivas (su contador nunca es cero pues apuntan a ellas mismas). La recoleccin de basura cuando casi todo el espacio de memoria est en uso se torna cada vez ms lenta, el reclamo de ms memoria por parte del programa es atendido ms lentamente (en cada nueva solicitud es ms difcil encontrar nodos disponibles, verificando el bit de marca). Una solucin parcial al problema es dejar que el programador defina un mnimo n de nodos para la ejecucin de su programa y si despus de una recoleccin de basura no se cubre este n el programa es interrumpido.

128

Algoritmos y Estructuras de Datos

Tambin se observa que en sistemas de tiempo real, la recoleccin de basura , presenta la gran desventaja de que el recolector entra en accin repetidas veces con el consiguiente consumo de tiempo. Los algoritmos de recoleccin de basura son aprovechables para el caso en que se desea marcar todos los nodos referidos directa o indirectamente por otro (llamados entre rutinas). La recoleccin de basura tiene dos fases: a) Se procede a marcar todos los nodos que no estn disponibles empezando por los que tiene asignados el programa. b) Recolecciona los nodos restantes (disponibles) en una lista de espacio libre (pool). A continuacin se plantea un algoritmo de recoleccin de basura. El algoritmo no es nico y para algunos casos concretos o algn tipo de mquina puede mejorarse. Se asume que los nodos tienen los siguientes campos: MARCA (1 bit) ATOMO (1 bit)

LIGA1 (apuntador) LIGA2 (apuntador) Cuando ATOMO=0, LIGA1 y LIGA2 son nulos (igual a NIL) o contienen un apuntador a otro nodo de igual estructura. Si ATOMO=1 el contenido de LIGA1 y LIGA2 no tiene importancia para el algoritmo. Dado un apuntador P0 se inicializa MARCA=1 en NODO(P0) y en todos los nodos que se puedan accesar desde NODO(P0) a travs de LIGA1 y LIGA2 y que tienen ATOMO=MARCA=0. Este algoritmo usa tres apuntadores P,Q y T. 1) T=NIL 2) P=P0 3) MARCA(P)=1 4) Si ATOMO(P) = 1, (9) 5) Q=LIGA1(P) 6) Si Q # NIL Y MARCA(Q) = 0, ATOMO(P)=1,LIGA1(P)=T, T=P,P=Q, (3) 7) Q=LIGA2(P) 8) Si Q # NIL Y MARCA(Q) = 0, LIGA2(P)=T, T=P,P=Q, (3) 9) Si T = NIL, FIN 10) Si ATOMO(Q) = 1 , ATOMO(Q)=0, T=LIGA1(Q), LIGA1(Q)=P P=Q, (7) 11) T=LIGA2(Q) 12) LIGA2(Q)=P 129

Algoritmos y Estructuras de Datos

13) P=Q 14) (7)


Ejercicio Escriba el algoritmo para el contador de referencias.

5.4 Administracin de memoria

Los programas de servicio cambian la fisonoma de la computadora de tal manera que facilitan el uso de la mquina. En particular, la imagen conceptual que hay sobre la memoria es tan sencilla que al usuario le permite centrarse en otros aspectos, sin embargo, tan slo un asignamiento que hiciere el usuario de un lenguaje de programacin obliga al anlisis de opciones inimaginadas. En esta seccin presentaremos los algoritmos que introducen al estudio de la administracin de memoria. Tomamos como base dos programas de servicio representativos, un sistema operativo y las rutinas de biblioteca de un compilador de un lenguaje de programacin. Estos permiten al usuario efectuar dos operaciones fundamentales: - peticin (allocate) - devolucin (dispose) Las peticiones de memoria pueden llevarse a cabo de manera regular o completamente aleatoria. En el primer caso hay reglas inherentes a las operaciones de memoria. Por ejemplo, "la memoria ocupada por las variables locales de un procedimiento no puede desaparecer mientras no termine el procedimiento". Cada vez que se invoca un procedimiento sabemos que se almacena en una pila la direccin "de regreso" y se transfieren parmetros. Adems, deben activarse las variables locales del procedimiento. Tales variables se definen contiguas a los parmetros, es decir en una zona especfica de la pila. A esta zona de memoria (dinmica) se le conoce como registro de activacin, y cada llamado a un procedimiento implica generar en la pila el registro de activacin correspondiente. As mismo cada regreso de procedimiento conlleva al desalojo del registro asociado. Existe por tanto un orden sobre las peticiones y devoluciones de memoria y su administracin se puede realizar con una estructura de pila. Cuando las peticiones son irregulares (por ejemplo "new" en PASCAL), tenemos que enfocar el problema de manera distinta. El problema es equivalente a tomar bloques de longitud variable de una zona de memoria disponible. Existen varios criterios para elegir estos bloques. Aqu slo veremos el criterio que considera la "recolocacin de la memoria". Dicho criterio se divide en dos algoritmos: - eleccin del bloque por orden (first fit) - eleccin del bloque por tamao (best fit) 130

Algoritmos y Estructuras de Datos

Consideremos antes un caso especial ms sencillo: los bloques de memoria se consideran fijos asignados por el operador al iniciar el trabajo con el sistema. Cada vez que se hace una peticin de memoria, una tabla que indica el tamao y origen de cada bloque disponible puede recorrerse para atender la peticin. Como podr observarse, en este caso hay pocas posibilidades de una atencin eficiente. Por ejemplo la disposicin fija de bloques puede desperdiciar memoria. Si a lo anterior le agregamos que se subutilicen bloques grandes, cuando una peticin de un bloque grande se haga, si acaso puede atenderse (haciendo movimientos entre los diferentes bloques ocupados) esta accin disminuira el rendimiento del sistema.

/////////// ocupado / (48)

120 k

60 k ////////// ocupado / (60) ////////////////////////////// // ////////////////////////////// 100 k

particin de memoria en 4 bloques de diferente tamao

48 k

Lo anterior es una implantacin rudimentaria de la atencin de peticiones de memoria. Es claro que por el dinamismo del proceso pensamos inmediatamente en una estructura de datos con almacenamiento encadenado. En efecto, cambiar lo anterior a esta representacin conduce a la siguiente caracterizacin: 1. Los bloques se eligen del tamao requerido, "separando" del rea libre la memoria pedida. 2. La memoria restante se mantiene organizada en una lista encadenada con "nodos" de longitud variable. 3. Las devoluciones de memoria se incorporan a la lista de memoria libre. Lo cual nos ofrece mayor libertad y adems eficiencia. Un algoritmo que concreta los puntos anteriores deber considerar nodos con un encabezado que indique su tamao y el apuntador al siguiente nodo. Tendramos entonces un nodo donde su primera localidad tiene el apuntador al siguiente nodo y en la segunda localidad su tamao. El algoritmo entonces queda: pide(n: integer; VAR p: pointer); BEGIN p:= lista[L]; q:= L; bus:= true; (*inicio*) WHILE (p <> nil) and bus DO BEGIN IF lista[p+1] >= n THEN BEGIN (*encuentra bloque*) 131

Algoritmos y Estructuras de Datos

lista[p+1]:= lista[p+1]-n; (*calcula resto*) IF lista[p+1]=0 THEN lista[q]:= lista[p](*bloque exacto*) ELSE p:= p+lista[p+1] (*incorpora resto*) END; bus:= false (*lo encontr*) END; IF bus THEN BEGIN q:=p; p:= lista[p] END (*pasa al sig*) END END {pide};

Analizando las operaciones de memoria observamos lo que se conoce como fragmentacin; pequeos bloques libres que no pueden ser reutilizados. Una posible solucin es la compactacin: todos aquellos bloques libres que estn contiguos pueden constituirse en uno slo, de esta manera disminuye la cantidad de bloques (se acelera la bsqueda) y a la vez pueden satisfacer demandas ms amplias. La compactacin slo es un paliativo de la fragmentacin, no la elimina y es comn incorporarla a la operacin de devolucin. Para lograr este planteamiento debe modificarse ligeramente el encabezado de los nodos. Incluimos cuatro campos ms: dos marcas, apuntador al nodo anterior y apuntador al inicio del bloque: p izq der tam ... marca inicio marca un nodo que representa un bloque de memoria

La distribucin de estos campos en la forma indicada en la figura (marca superior, inferior e inicio) no es casual. La marca indica si el bloque est ocupado de tal forma que un bloque que es devuelto tiene acceso, a travs de su apuntador y tamao, a los bloques adyacentes y conocer mediante sus marcas si est libre. El apuntador "inicio" que aparece al final ayuda a compactar el nodo con otro que es devuelto. En trminos de un algoritmo tendramos:

peticin(n: integer; VAR p: pointer); BEGIN p:=lista[L]; REPEAT IF lista[p+1] >= n THEN BEGIN (*lo encuentra*) delta:= lista[p+1]-n; IF delta < epsilon THEN BEGIN (*evita fragmentar*) 132

Algoritmos y Estructuras de Datos

lista[lista[p-1]]:= lista[p]; (*actualiza*) lista[lista[p]-1]:= lista[p-1]; lista[p+2]:= lista[p+2+lsita[p+1]-1]:= 1; L:= lista[p-1] END ELSE BEGIN lista[p+1]:= delta; lista[p+delta-1]:= p; (*inicio*) lista[p+delta]:= 0; L:= p; (*fin de bsqueda*) p:= p+delta; lista[p+1]:= n; lista[p+2]:= lista[p+2+n-1]:= 1 END; p:= lista[p] (*siguiente*) END UNTIL p:=lista[L] END {peticin};

Por otra parte, abreviando la notacin del algoritmo de devolucin tenemos:

devolucin(p: pointer); BEGIN n:= lista[p+1]; CASE OF :lista[p-3]=1 AND lista[p+n+2]=1: (*adyacentes en uso*) lista[p+2]:= lista[p+n-2]:= 0 (*marcas libre*) lista[p+n-3]:= p (*inicio*) lista[p-1]:= L; (*inserta*) lista[p]:= lista[L]; lista[lista[p]-1]:= p; lista[L]:= p :lista[p+n+2]=1 AND lista[p-3]=0: (*libre izquierda*) q:= lista[p-2]; (*inicio izquierda*) lista[q+1]:=lista[q+1]+n (*aumenta tama$o*) lista[p+n-3]:= q; (*inicio*) lista[p+n-2]:= 0 (*marca final*) :lista[p+n+2]=0 AND lista[p-3]=1: (*libre derecho*) lista[lista[p+n-1]]:= p; (*inserta*) lista[lista[p+n]-1]:= p; lista[p-1]:= lista[p+n-1]; (*actualiza encabez*) lista[p]:= lista[p+n]; lista[p+1]:= n+lista[p+n+1]; lista[p+lista[p+1]]:= p lista[p+2]:= 0; IF L=(p+n) THEN L:=p 133

Algoritmos y Estructuras de Datos

:ELSE: (*ambos libres*) lista[lista[p+n-1]]:= lista[p+n]; lista[lista[p+n]-1]:= lista[p+n-1]; q:= lista[p-2]; lista[q+1]:= lista[q+1]+n+lista[p+n+1] lista[q+lista[q+1]]:= q; IF L=(p+n) THEN L:= lista[p+n-1] END {devolucin};

Las anteriores operaciones representan el criterio de elegir el bloque pedido segn el orden en que aparecen en la lista de libres. Sin embargo esto puede ser "mejorado", en el sentido de obtener el ms pequeo que satisfaga la demanda. A este ltimo criterio se le conoce como "best-fit" y constituye una variacin de los anteriores
Ejercicio Escribir el algoritmo "best-fit".

134

Algoritmos y Estructuras de Datos

Bibliografa
1. Aho, Alfred V., Hopcroft, John E., Ullaman, Jeffrey D. Estructuras de Datos y Algoritmos. Ed. Addison -Wesley Iberoamericana, 1988. 2. Allen, Weiss Mark, Estructuras de Datos en JAVA, Addison Wesley 1998. 3. Allen, Weiss Mark, Estructuras de datos y algoritmos, Ed. Addison -Wesley Iberoamericana, 1998 4. Kruse L., Estructuras de Datos y Diseo de Programas, Prentice Hall, 1996 5. Cormen H. Thomas., Leiserson E. Charles, Rivest L. Ronald, Introduction to Algorithms, MIT Press, 1990 6. Wirth, N., Algorithms + Data Structures = Programs, Prentice Hall, 1976 7. Jean-Paul Tremblay, Paul G., An Introductions to Data Structures with Applications, Mc Graw Hill International Ed. 8. Wirth N., Algoritmos y Estructuras de Datos, Prentice Hall Hispanoamericana Mxico, 1987. 9. G. Brassard, T. Bratley, Fundamentos de Algoritmia, Prentice Hall, 1997 10. Deitel y Deitel, Como programar en JAVA, Prentice Hall, 2000. 11. Herbert Shildt, Java, Manual de referencia, Mc Graw Hill, 1997 12. Sisa, Alberto J. Estructuras de Datos y Algoritmos (con nfasis en POO). Ed. Prentice Hall. Sitios Web Visitados

http://delfosis.uam.mx/~sgb/grafos/default.html

135