ESTRUCTURAS DE DATOS OTOÑO 2014 DR. MARIO ROSSAINZ LOPEZ UNIDAD 3: Estructuras de Datos Dinámicas Se sabe que la memoria es un elemento indispensable para llevar a cabo operaciones sobre objetos del mundo real. En la memoria es posible describir los objetos mediante una serie de características y trabajar con ellos de manera abstracta. Por otro lado, el modelo actual de computadora dispone de una memoria con unidades capaces de mantener información fundamentalmente numérica. Por su definición y tecnología estas unidades constan de una dirección y contenido 00000000 Dirección Contenido UNIDAD O LOCALIDAD DE MEMORIA La información se haya distribuida en un espacio que hereda características secuenciales fijas. No es posible disponer objetos tal como en el espacio real lo hacemos. El almacenamiento dinámico pretende definir estructuras de información para poder operar con estas características de los objetos como sucede en el mundo real. 3.1.- Apuntadores. Dentro de las características de un lenguaje de programación existe cierto tipo de estructuras con capacidad de cambiar de tamaño que las distingue claramente de las estructuras fundamentales como son arreglos, registros o conjuntos (ya vistos). La técnica que se utiliza para trabajar este tipo de estructuras implica la asignación dinámica de almacenamiento, es decir, asignación de espacio en la memoria para componentes individuales en el momento en que entran en existencia durante la ejecución de un programa, en vez de en un instante de transición. El compilador asigna después una cantidad fija de almacenamiento para contener la dirección de la componente asignada dinámicamente y no de la componente misma. Por ejemplo, el árbol genealógico que se ilustra a continuación será representado por registros individuales, uno por cada persona. Estas personas se vinculan por sus direcciones asignadas a los campos digamos de padre y madre respectivos (véase el uso de flechas o apuntadores en la figura. ESTRUCTURAS DE DATOS OTOÑO 2014 DR. MARIO ROSSAINZ LOPEZ TED FRED MARY ADAM F F EVA F F F F Actualmente se ha vuelto común en los lenguajes de programación avanzados hacer posible la manipulación explícita de referencias de datos (mediante apuntadores) además de los datos mismos. Esto implica que debe existir una distinción notacional entre los datos y las referencias de estos y que en consecuencia debe introducirse tipos de datos cuyos valores sean apuntadores (referencias) de otros datos. Un apuntador es una celda, unidad o localidad de memoria cuyo valor indica, señala o apunta a otra localidad mediante una dirección. La notación que se emplea para este objeto es la siguiente: Tipo T= Apuntador a tipo_elemento Esta declaración indica que los valores del tipo T son apuntadores de datos del tipo tipo_elemento. Así, cuando se representan gráficamente estructuras de datos, el hecho de que una localidad A sea un apuntador a la localidad B, se indica con una flecha de A a B. En PASCAL por ejemplo, se puede crear una variable ap que apunte a localidades de memoria de un tipo determinado de datos mediante la declaración: var ap: tipo_elemento De acuerdo con esto, la expresión ap denota el valor (de tipo_elemento) de la localidad a la que apunta ap. ESTRUCTURAS DE DATOS OTOÑO 2014 DR. MARIO ROSSAINZ LOPEZ Decimos entonces que los valores de los tipos apuntadores se generan siempre que un elemento de datos es asignado dinámicamente. ap: t 11110010 11110010 ap: t ASIGNACION DINAMICA DE VARIABLE Regresando a la figura del árbol genealógico, se vuelve a mostrar a continuación, sólo que ahora (siguiendo con la distinción notacional) los apuntadores de personas desconocidas se simbolizan por la palabra NIL ( o NULL en otros casos). TED FRED ADAM NIL NIL NIL MARY NIL EVA NIL NIL Volviendo a hacer referencia a la figura de arriba, supóngase que Fred y Mary son hermanos, es decir, tienen el mismo padre y madre. Esta situación se expresa fácilmente reemplazando los dos valores NIL en los campos respectivos de los dos registros. Considerar a los apuntadores como elementos de datos explícitos en vez de cómo auxiliares ocultos en la instrumentación permite al programador expresar con claridad donde se intenta establecer compartición de almacenamiento y donde no. En secciones posteriores dedicaremos el estudio de la generación y manipulación de estructuras de datos cuyas componentes se vinculan por medio de apuntadores explícitos. ESTRUCTURAS DE DATOS OTOÑO 2014 DR. MARIO ROSSAINZ LOPEZ 3.2.- Implantación de listas mediante apuntadores. Otra forma de implantar una lista como opción alternativa a la presentada en el capítulo anterior, es utilizar apuntadores para enlazar elementos consecutivos bajo esta estructura de datos abstracta. Esta implantación permite eludir el empleo de memoria contigua para almacenar una lista, y por tanto también elude los desplazamientos de elementos para hacer inserciones o rellenar vacíos creados por la eliminación de elementos. Sin embargo el precio que hay que pagar es el de un espacio adicional para los apuntadores. En este sentido una lista (enlazada) consta de una serie de registros que no necesariamente son adyacentes en memoria. Cada registro contiene el elemento y un apuntador a un registro que contiene su sucesor. A éste se le llama el apuntador siguiente (sig). El apuntador siguiente de la última celda apunta a nil cuyo valor esta definido en la mayoría de los lenguajes de programación como lo es PASCAL o C y no puede confundirse con ningún otro apuntador. Un valor característico utilizado para nil es regularmente el valor 0 (cero). La siguiente figura muestra una lista enlazada de esta manera: clientes nil nil empleados Listas ligada basada en apuntadores Recordemos que una variable apuntador sólo es una variable que contiene la dirección donde otro dato está almacenado. Así, si p se declara como apuntador a un registro, el valor almacenado en p se interpretará como el lugar, en memoria principal, donde se puede encontrar un registro. ESTRUCTURAS DE DATOS OTOÑO 2014 DR. MARIO ROSSAINZ LOPEZ Para tener acceso a un campo del registro se emplea se emplea p.nombre_campo donde nombre_campo es el nombre de campo que se desea examinar. La siguiente figura muestra la representación real de la lista mostrada anteriormente clientes 800 712 992 692 0 1000 800 712 992 692 La lista contiene 5 registros, los cuales están en las posiciones de memoria 1000, 800, 712, 992 y 692, respectivamente. El apuntador siguiente del primer registro tiene el valor 800 y da la indicación de donde se encuentra el segundo registro. Lo mismo pasa con los demás. Obviamente, a fin de tener acceso a la lista se necesita conocer donde se puede encontrar la primer celda de la lista (encabezado o header). Una variable apuntador puede servir para este propósito. Hay que recordar que un apuntador sólo es un número aunque para hacer más gráfica las ilustraciones, se utilizarán las flechas. El tipo de una lista es entonces el mismo que el de una posición; es un apuntador a una celda particular: el encabezado. La siguiente es una definición formal de las partes esenciales de una estructura de datos de lista enlazada: Tipo ap_nodo = nodo nodo= Registro elemento: tipo_elemento sig: ap_nodo Fin LISTA = ap_nodo posición = ap_nodo El siguiente pseudocódigo muestra la función Esta_vacia(L) la cual verifica si la lista esta o no vacía, es decir, si el apuntador del inicio de la lista es o no nil. ESTRUCTURAS DE DATOS OTOÑO 2014 DR. MARIO ROSSAINZ LOPEZ Función Esta_vacia(L:LISTA): boolean Inicio Si L.sig=nil Entonces regresa true Sino regresa false Fin Función para comprobar si la lista enlazada está vacía Las siguientes rutinas implementan las operaciones más comunes que se realizan en el manejo de listas con apuntadores. Funcion es_ultimo(p: posición; L:LISTA): booleano Inicia Si p.sig=nil Entonces regresa true Sino regresa false fin Función para comprobar si la posición actual es la última en la lista enlazada Funcion Buscar(x:tipo_elemento;L:LISTA): posicion Inicia Variable p: posición encontrado: booleano encontrado falso p L.sig mientras (p <> nil) y (no encontrado) hacer Si p .elemento = x Entonces encontrado true Else p p .sig Regresa p Fin Función para buscar un elemento en la lista enlazada ESTRUCTURAS DE DATOS OTOÑO 2014 DR. MARIO ROSSAINZ LOPEZ Procedimiento Insertar(x:tipo_elemento; L:LISTA; p:posicion) Inicia Variable Celda_Temp: posición New(celda_Temp) Celda_temp.elemento x Celda_temp.sig p.sig p.sig celda_temp Fin Rutina de inserción para listas enlazadas Procedimiento Eliminar_lista(var L:LISTA) Inicia Variable p,temp: posición p L.sig L.sig nil Mientras p <> nil hacer Temp p.sig dispose(p) p Temp Fin Forma correcta de eliminar una lista 3.2.1.- Listas doblemente enlazadas. En algunas aplicaciones puede ser deseable recorrer una lista ligada tanto hacia delante como hacia atrás. O bien, dado un elemento podría desearse determinar con rapidez el siguiente y el anterior nodo a dicho elemento. La implantación estandar no ayuda en esto pero la solución es simple: Sólo se añade un campo adicional a la estructura de datos, que contiene un apuntador a la celda anterior. Véase la siguiente figura: ESTRUCTURAS DE DATOS nil OTOÑO 2014 DR. MARIO ROSSAINZ LOPEZ nil LISTA DOBLEMENTE LIGADA El costo de esto es la presencia de un apuntador adicional en cada nodo y los procedimientos más lentos para algunas de las operaciones básicas con listas. La declaración de un nodo de una lista ligada podría ser la siguiente: Tipo ap_nodo = nodo nodo= Registro elemento: tipo_elemento sig,ant: ap_nodo Fin LISTA = ap_nodo posición = ap_nodo 3.3.- Implantación de pilas mediante apuntadores. Una pila se puede ver también como una lista, por tanto, cualquier implantación de listas enlazadas de forma simple funciona para este tipo de dato abstracto. Analicemos entonces dicha implantación. De acuerdo a las operaciones naturales de una pila (ya vistas en el capítulo anterior), se realiza una operación meter insertando un nodo al frente de la lista. Se efectúa una operación sacar eliminando el elemento o nodo que esta al frente de la lista. Una operación tope sólo examina el elemento del frente de la lista, devolviendo su valor. A veces las operaciones sacar y tope se combinan en una. A continuación se dan las definiciones en pseudocódigo de esta forma de implantación: ESTRUCTURAS DE DATOS Tipo OTOÑO 2014 DR. MARIO ROSSAINZ LOPEZ ap_nodo = nodo nodo= Registro elemento: tipo_elemento sig: ap_nodo Fin PILA = nodo La implantación para saber si una pila esta o no vacía se lleva a cabo de la misma manera que en el caso de la implantación con arreglos pero ahora con apuntadores, es decir: Función vacia(p: PILA): booleano Inicia Si psig =nil Entonces regresa verdadero Sino regresa falso fin La creación de una pila vacía también es sencilla. Basta con crear una cabecera con un apuntador sig a nil. Procedimiento crear_vacia(var p:PILA) Inicia New(p) psig =nil Fin El meter se implanta como inserción al frente de la lista enlazada, donde el frente de la lista sirve como el tope de la pila. Procedimiento meter(x:tipo_elemento; p:PILA) var celda_temp: nodo Inicia New(celda_Temp) Si celda_temp=nil Entonces error(“Pila llena”) Sino celda_temp.elemento x celda_temp.sig p.sig p.sig celda_temp Fin ESTRUCTURAS DE DATOS OTOÑO 2014 DR. MARIO ROSSAINZ LOPEZ El tope o cima se efectúa examinando el elemento de la primera posición de la lista: Función cima(var p:PILA ): tipo_elemento Inicia Si vacia(p) Entonces error(“Pila vacía”) Sino regresa(p.elemento) Fin Finalmente la implantación de la operación sacar que elimina el nodo del frente de la lista es la siguiente: Procedimiento sacar(p:PILA) var celda_temp: nodo Inicia Si vacia(p) Entonces error(“pila vacia”) Sino celda_Temp p .sig p.sig p.sig.sig dispose(celda_temp) Fin 3.3.- Implantación de colas mediante apuntadores. Igual que en el caso de las pilas, cualquier implantación de listas es lícita para las colas. Así, para aumentar la eficacia de operaciones tales como “pon en la cola”, es posible aprovechar el hecho de que las inserciones se efectúan sólo en el extremo posterior de ésta estructura de datos, es decir; en lugar de recorrer la lista de principio a fin cada vez que se desea insertar un elemento o nodo, se puede mantener un apuntador al último elemento. La implantación de colas que aquí se desarrolla se basa en apuntadores. Por tanto es posible definir una cola como una estructura que consiste en apuntadores al extremo anterior de la lista y al extremo posterior. Esta convención permite representar de manera simple una cola vacía: ESTRUCTURAS DE DATOS OTOÑO 2014 DR. MARIO ROSSAINZ LOPEZ Tipo ap_nodo = nodo nodo= Registro elemento: tipo_elemento sig: ap_nodo Fin COLA= Registro ant,post: nodo Fin A continuación se muestran las implantaciones de las cinco operaciones utilizadas con mayor frecuencia en una estructura de datos tipo cola Procedimiento anula(var c: COLA) Inicio New(c.ant) c.ant.sig nil c.post c.ant fin Procedimiento que crea una cola con encabezado en la primera y ultimas celdas c.ant c.post ANULA ESTRUCTURAS DE DATOS OTOÑO 2014 DR. MARIO ROSSAINZ LOPEZ Funcion vacia(c:COLA):booleano Inicio Si c.ant = c.post Entonces regresa true Sino regresa falso Fin Función que verifica si una cola esta o no vacía Funcion Frente(c:COLA): tipo_elemento Inicio Si vacia(c) Entonces error(“la cola esta vacia”) Sino regresa (c.ant.sig.elemento) Fin Función que regresa el elemento que se encuentra al frente de la cola Procedimiento pon_en_cola(x:tipo_elemento; var c:COLA) Inicio New(c.post.sig) c.post c.post.sig c.post.elemento x c.post.sig nil Fin Procedimiento que coloca en la cola un elemento c.ant x c.post PON_EN_COLA(x,c) ESTRUCTURAS DE DATOS OTOÑO 2014 c.ant DR. MARIO ROSSAINZ LOPEZ x y c.post PON_EN_COLA(y,c) Procedimiento quita_de_cola(var c:COLA) Inicio Si vacia(c) Entonces error(“la cola esta vacía”) Sino c.ant c.ant.sig Fin Procedimiento que elimina o quita un elemento de la cola c.ant x y c.post QUITA_DE_COLA(c)
© Copyright 2024