1. Desarrollar los siguientes temas y anexa un ejemplo para cada uno de ellos: a) Arreglos unidimensionales b) Arreglos bidimensionales bidimensionales c) Pilas d) Listas a. Listas doblemente ligadas b. Listas circulares 2. Explicar cómo funcionan los recorridos de árboles binarios en: a) Pre-orden b) Orden c) Post-orden
Arreglos unidimensionales (en Java). Definición. Es una colección de datos del mismo tipo. Sirve para manejar un número “n” de elementos en e n común, ya sea de tipos definidos por el Lenguaje, (“int”, “float”, “String”, etc.) así como aquellos definidos por el programador. Los arreglos en Java son dinámicos, pero no extensibles, lo cual significa que deben ser creados con el tamaño que tendrán hasta el final de su vida. Características: Son de tipos de datos complejos (en el sentido de que no son atómicos). Agrupan varios (1 o más) fatos de un mismo tipo, llamando el tipo base del arreglo. Son dinámicos, pero no extensibles, lo cual significa que deben ser creados con el tamaño que tendrán hasta el final de su vida. Los índices del arreglo empiezan con 0 y terminan con el tamaño del arreglo -1. a[ n -1]. Si el arreglo tiene n elementos, se denotan como a[0], a[1], … , a[n El índice en un arreglo empieza siempre desde CERO. Los arrays se crean con el operador new seguido del tipo y número de elementos. Declaración de un arreglo. La sintaxis de declaración de arreglos en Java es: []
Para declarar, por ejemplo, un arreglo de números enteros utilizaremos la siguiente sentencia: int[] arrInt;
Instituto Politécnico Nacional
ESIME Unidad Culhuacán
El arreglo aún no ha sido creado, sino meramente declarado. Para crear el arreglo (reservar su memoria e inicializarlo) deberemos recurrir al operador new: arrInt = new int[10];
El tipo de datos del arreglo ( int[] en este caso) es una clase y cada una de sus instancias debe ser creada explícitamente, el tamaño puede pensarse como un parámetro al constructor de la clase. A partir de este momento se puede utilizar arrInt como un arreglo de cualquier otro lenguaje. Una de las características que hacen de Java un entorno de programación seguro, y que se relaciona con el manejo de los arreglos es que el lenguaje no permite la indexación de arreglos fuera de rango, o sea, una asignación de este tipo generará una excepción: ArrInt[25] = 1;
O también se puede declarar de la siguiente forma: tipo nombre_array[]=new tipo[nº]; tipo nombre_array[]={valores};
Tamaño de un arreglo. Java considera cada arreglo como un solo objeto. El número de elementos de un arreglo se conoce accediendo al campo
length double [] v = new double [15] System.out.printl(v.length); //escribe 15 El campo length está protegido, no se puede modificar
Inicializando el arreglo. 1. Los arrays se pueden inicializar con valores entre llaves {...} separados por comas. 2. También los arrays de objetos se pueden inicializar con varias llamadas a new dentro de unas llaves {…} 3. Si se igualan dos referencias a un array no se copia el array, sino que se tiene un array con dos nombres, apuntando al mismo y único objeto. 4. Creación de una referencia a un array, se puede hacer de dos maneras: double[] x; //preferible double x[];
5. Creación del array con el operador new x = new double[100];
6. Las líneas 4 y 5 se pueden unir en una sola: double[]x = new double[100];
Arreglos de caracteres y cadenas de texto. Una cadena de texto es un conjunto de caracteres, „abcdefg‟. Son soportados por la clase String: String cadena = „abcdefg‟
2
Instituto Politécnico Nacional
ESIME Unidad Culhuacán
Los arreglos tipo char son secuencias de caracteres, con características de arreglos de otros tipos: String mas = “programador Java” char datos[] = {„D‟,„i‟,„r‟,„e‟,„c‟,„t‟,„o‟,„r‟,„i‟,„o‟};
Ejemplo: Código:
Ejecución:
3
Instituto Politécnico Nacional
ESIME Unidad Culhuacán
Arreglos bidimensionales (en Java). Definición. Es un arreglo de arreglos, es decir, un arreglo unidimensional donde cada elemento es un arreglo. Los arreglos bidimensionales son muy utilizados para el uso de matrices de datos, y poder recorrer los datos guardados dentro de este tipo de arreglos. Características: Se hace utilizando anidamiento de la estructura de control for( ) Dos índices en lugar de uno Dimensión: n × m Fila, Columna: [x][y] Si A es un arreglo bidimensional A[2][4] es un valor especifico en el arreglo. Se pueden ver como matrices. Declaración de un arreglo bidimensional. [][]
También se puede declarar: [][] int tabla[][]; tabla = new int[4][2]; int tabla[][]={valores};
Elemento Tabla[0][0] Tabla[0][1] Tabla[1][0] Tabla[1][1] Tabla[2][0] Tabla[2][1] Tabla[3][0] Tabla[3][1]
Posición relativa en memoria 0 4 8 12 16 20 24 28
Inicializando el arreglo. tabla = new int[4][2]; //4 serán los renglones, mientras 2 //serán las columnas int tabla[][]={{51,52},{54,55},{51,52},{54,55}};
4
Instituto Politécnico Nacional
ESIME Unidad Culhuacán
Ejemplo: Código:
Ejecución:
5
Instituto Politécnico Nacional
ESIME Unidad Culhuacán
Etc.
Pilas. Definición. En las pilas, los ítems se añaden y se eliminan en el mismo extremo. Se conocen como colas LIFO (ultimo en entrar, primero en salir).
6
Instituto Politécnico Nacional
ESIME Unidad Culhuacán
Operaciones con pilas.
Aplicaciones de las pilas. Proporcionan un medio ordenado de demorar la realización de las tareas secundarias que aparecen durante la ejecución del programa Suelen ir asociadas a algoritmos recursivos Tipos derivados: pilas de programas, pila del analizador sintáctico (parser) Interfaz stack. public interface Stack { public boolean isEmpty(); public void push( Object o); public Objetct pop() throws EmptyStackExceptio; public void clear(); }
Inversión mediante pila del orden de un array. public class Reverse { public static void main(String args[]) { int [] array = { 1, 2, 3, 4, 5 }; int i; Stack stack = new ArrayStack(); for ( i = 0; i < array.length; i++ ) stack.push( new Integer( array[ i ] )); i = 0; while ( !stack.isEmpty() ) { array[ i ] = ((Integer) stack.pop()).intValue(); System.out.println( array[ i++ ] ); } } }
7
Instituto Politécnico Nacional
ESIME Unidad Culhuacán
Implementación de pilas basada en arrays. En esta implementación basada en arrays, la posición en el array del elemento cima de la pila va bajando a medida que se insertan ítems, y subiendo a medida que se sacan. El inicio de la pila se hallará siempre en el elemento 0 del array. En cierto sentido, la pila se va formando "cabeza abajo" en el array.
Ejemplo. public boolean agregarPila (Object o) { /* pregunto si la pila está llena. */ if (puntero == pila.length) { /* Si es que está lleno no se pueden agregar más registros, por lo tanto retorno falso. */ return false; /* Caso Contrario */ } else { /* Significa que la pila aún no está llena y puedo seguir * agregando registros. Y agrego en la posición puntero. */ pila[puntero]=o; /* Incremento el puntero para que se desplace hacia la siguiente * posición. */ puntero++; /* Retorno verdadero, porque se agregó el objeto. */ return true; } }
Listas (en Java). Definición. Las listas (List) aparecen en los interfaces de usuario para facilitar a los operadores la manipulación de muchos elementos. En java este componente permite procesar visualmente un conjunto de elementos de tipo string.
8
Instituto Politécnico Nacional
ESIME Unidad Culhuacán
Su primer y más importante aspecto a recordar, cuando se procese o programe, es que el primer índice de la lista, es el índice numero 0(cero).
Ejemplo: package ListaEnteros; // clase Nodo con las dos partes de un nodo y su constructor public class Nodo { int dato; Nodo enlace; public Nodo(int x) { dato = x; enlace = null; } } /* clase Lista con las operaciones: insertar por la cabeza y visualizar (recorre los nodos) para mostrar los datos. Además, el atributo primero, que apunta al primer nodo. */ package ListaEnteros; public class Lista { private Nodo primero; public Lista() { primero = null; } public Lista insertarCabezaLista(int entrada) { Nodo nuevo ; nuevo = new Nodo(entrada); nuevo.enlace = primero; primero= nuevo; return this; } public void visualizar() { Nodo n; int k = 0; n = primero; while (n != null) { System.out.print(n.dato + " ");
9
Instituto Politécnico Nacional
ESIME Unidad Culhuacán
n = n.enlace; k++; System.out.print( (k%15 != 0 ? " " : "\n")); } } } // clase con método main
import java.util.*; import ListaEnteros.*; public class ListaAleatoria { public static void main(String [] a) { Random r; int d; Lista lista; int k; r = new Random(); lista = new Lista(); // crea lista vacía k = Math.abs(r.nextInt() % 55); // número de nodos // Son insertados elementos en la lista for (; k > 0; k-- ) { d = r.nextInt() % 99 ; lista.insertarCabezaLista(d); } // recorre la lista para escribir sus elementos System.out.println("Elementos de la lista generados al azar"); lista.visualizar(); } }
Listas doblemente ligadas. Definición. Una lista doble, ó doblemente ligada es una colección de nodos en la cual cada nodo tiene dos punteros, uno de ellos apuntando a su predecesor (li) y otro a su sucesor (ld). Por medio de estos punteros se podrá avanzar o retroceder a través de la lista, según se tomen las direcciones de uno u otro puntero. Ejemplo: public class ListaEnlazada { public NodoListaEnlazada PrincipioFin; private int numNodos;
10
Instituto Politécnico Nacional
ESIME Unidad Culhuacán
// . . . }
El nodo especial PrincipioFin es sencillo, para simplificar el código. El contador se usa para optimizar los casos más habituales. Revisemos pues el código de nuestro Hotel, ahora Hotel3.java, que será prácticamente el mismo que en el caso de los arrays: public class Hotel3 { // Número de habitaciones por ala public static final int habPorAla = 12; public static void main( String args[] ) { ListaEnlazada llaveMaestra; llaveMaestra = new ListaEnlazada();
// paso 1 // pasos 2-5
int numPiso = 1; for( int i=0; i < habPorAla; i++ ) // pasos 6-9 llaveMaestra.insertAt( i, new Habitacion( numPiso * 100 + i, ( 0 == (i%2)) ? 2 : 1 ); for( int i=0; i < habPorAla; i++ ) // pasos 10-12 ( (Habitacion)llaveMaestra.getAt(i) ).printData(); } }
El paso 1 es la llave maestra de la lista. Está representada por una lista genérica; es decir, una lista de llaves que cumple la convención que nosotros hemos establecido. Podríamos acelerar el tiempo de compilación metiendo la lista genérica ListaEnlazada dentro de una ListaEnlazadaHabitacion. Los pasos 2 a 5 son equivalentes a los del primer ejemplo. Construimos e inicializamos una nueva ListaEnlazada, que usaremos como juego de llaves maestras. Los pasos 6 a 9 son funcionalmente idénticos a los del ejemplo anterior con arrays, pero con diferente sintaxis. En Java, los arrays y el operador [] son internos del lenguaje. Como Java no soporta la sobrecarga de operadores por parte del usuario, tenemos que usarlo siempre en su forma normal. La ListaEnlazada proporciona el método insertAt() que coge el índice en la lista, donde el nuevo nodo ha de ser insertado, como primer argumento. El segundo argumento es el objeto que será almacenado en la lista. Obsérvese que no es necesario colocar moldeo alguno para hacer algo a una clase descendiente que depende de uno de sus padres. Los pasos 10 a 12 provocan la misma salida que los pasos 10 y 11 del ejemplo con arrays. El paso 10 coge la llave del juego que se indica en el método getAt(). En este momento, el sistema no sabe qué datos contiene la llave, porque el contenido de la habitación es genérico. Pero nosotros sí sabemos lo que hay en la lista, así que informamos al sistema haciendo un moldeado a la llave de la habitación (este casting
11
Instituto Politécnico Nacional
ESIME Unidad Culhuacán
generará un chequeo en tiempo de ejecución por el compilador, para asegurarse de que se trata de una Habitación). El paso 12 usa la llave para imprimir la información.
Listas circulares. Definición. Una lista circular es una lista lineal en la que el último elemento apunta al primero. Entonces es posible acceder a cualquier elemento de la lista desde cualquier punto dado. Las operaciones sobre una lista circular resultan más sencillas, ya que se evitan casos especiales. La siguiente figura muestra como se ve una lista circular simplemente enlazada.
Cuando recorremos una lista circular, diremos que hemos llegado al final de la misma cuando nos encontremos de nuevo en el punto de partido, suponiendo, desde luego, que el punto de partida se guarda de alguna manera en la lista; por ejemplo, con una referencia fija al mismo. Esta referencia puede ser al primer elemento de la lista; también puede ser al último elemento, en cuyo caso también es conocida la dirección del primer elemento. Creación de una lista circular. Primero se tiene que definir la clase de objetos que van a formar parte de la misma. Por ejemplo, cada elemento de la lista puede definirse como una estructura de datos con dos miembros: una referencia al elemento siguiente y otra al área de datos. El área de datos puede ser un tipo predefinido o de un tipo definido por el usuario. Según esto, el tipo de cada elemento de la lista puede venir definido de la forma siguiente: private class CElemento { //atributos private Object datos; private CElementos siguiente; //siguiente elemento //métodos private CElemento(){} //constructor private CElemento(Object d, CElemento s)//constructor { datos = d; siguiente = s; }
12
Instituto Politécnico Nacional
ESIME Unidad Culhuacán
}
Ejemplo. Código: Se debe declarar una clase que sea un nodo o elemento en la cual tengas una referencia al siguiente nodo de la lista. public class Elemento { private Elemento elementoSiguiente; public Elemento getElementoSiguiente() { return elementoSiguiente; } public void setElementoSiguiente(Elemento elementoSiguiente) { this.elementoSiguiente = elementoSiguiente; } }
Luego puedes crearte una lista a la cual le añadas los elementos, dentro de esta clase puedes usar un Collection o un array o un List. Esta lista es solo para poder acceder a los elementos de forma más fácil. Al momento de crear un nuevo elemento lo asignas al elemento anterior. List list = new ArrayList(); Elemento elemento = (Elemento)list.get(list.size()-1); elemento.setSiguienteElemento(new Elemento());
Recorridos de árboles binarios. Hay varios algoritmos para el manejo de estructuras en árbol y un proceso que generalmente se repite en estos algoritmos es el de recorrido de un árbol. Este proceso cosiste en examinar sistemáticamente los nodos de un árbol de forma que cada nodo sea visitado solamente una vez. Se pueden utilizar tres formas para recorrer un árbol binario: pre-orden, inorden y post-orden.
13
Instituto Politécnico Nacional
ESIME Unidad Culhuacán
Recorrido en forma pre-orden. Cuando se utiliza la forma pre-orden, primero se visita la raíz después el subárbol izquierdo y por último se visita el subárbol derecho. El recorrido en pre-orden produce la notación prefija.
Pre-orden: R, I, D
Ejemplo: Código: ///// clase árbol binario. public class CArbolBinario { //atributos del árbol binario private CNodo raíz; //raíz del árbol //nodo de un árbol binario private class CNodo { //atributos private Object datos; private CNodo izquierdo; private CNodo derecho; //métodos public CNodo(){}
//referencia a los datos //raíz del subárbol izquierdo //raíz del subárbol derecho //constructor
} //métodos del árbol binario public CArbolBinario(){} //constructor public void preorden(CNodo r) { if ( r != null ) { //escribir aquí las operaciones a realizar con el nodo //referenciado r preorden( r.izquiero); //se visita el subárbol izquierdo preorden( r.derecho); //se visita el subárbol derecho } }
Recorrido en forma in-orden. Primero se visita el subárbol izquierdo, después la raíz y por último el subárbol derecho. El recorrido en in-orden produce la notación convencional.
In-orden: I, R, D
14
Instituto Politécnico Nacional
ESIME Unidad Culhuacán
Ejemplo. Código: ///// Clase árbol binario. public class CArbolBinario { //atributos del árbol binario private CNodo raíz; //raíz del árbol //nodo de un árbol binario private class CNodo { //atributos private Object datos; private CNodo izquierdo; private CNodo derecho; //métodos public CNodo(){}
//referencia a los datos //raíz del subárbol izquierdo //raíz del subárbol derecho //constructor
} //métodos del árbol binario public CArbolBinario(){} //constructor public void inorden(CNodo r) { if ( r != null ) { inorden( r.izquierdo); //se visita el subárbol izquierdo //escribir aquí las operaciones a realizar con el nodo //referenciado r inorden( r.derecho); // se visita el subárbol derecho } }
Recorrido en forma post-orden. Primero se visita el subárbol izquierdo, después el subárbol derecho y por último la raíz. El recorrido en post-orden produce la notación postfija o inversa.
Post-orden: I, D, R
Ejemplo. Código: ///// clase árbol binario. public class CArbolBinario { //atributos del árbol binario private CNodo raíz; //raíz del árbol
15
Instituto Politécnico Nacional
//nodo de un árbol binario private class CNodo { //atributos private Object datos; private CNodo izquierdo; private CNodo derecho; //métodos public CNodo(){}
ESIME Unidad Culhuacán
//referencia a los datos //raíz del subárbol izquierdo //raíz del subárbol derecho
//constructor } //métodos del árbol binario public CArbolBinario(){} //constructor public void postorden(CNodo r) { if ( r != null ) { postorden( r.izquierdo);
//se visita el subárbol izquierdo postorden( r.derecho); //se visita el subárbol derecho //escribir aquí las operaciones a realizar con el nodo //referenciado r
} }
16