You are on page 1of 29

Estructura de Datos y Listas Encadenadas Avanzadas

(Septiembre-2011)

Contenido
INTRODUCCIN ....................................................................................................................................... - 1 LISTAS ENCADENADAS DE DOBLE ENLACE................................................................................................ - 1 CDIGO FUENTE DE NODE.CS ....................................................................................................................... - 2 CDIGO FUENTE DE LIST.CS PARA LISTAS ENCADENADAS DE DOBLE ENLACE .............................................................. - 4 IMPLEMENTAR EL RECORRIDO DE UNA LISTA MEDIANTE ENUMERADORES ............................................ - 8 CDIGO FUENTE PARA PROBAR LA IMPLEMENTACIN ....................................................................................... - 11 IMPLEMENTAR NDICES EN UNA LISTA .................................................................................................. - 13 IMPLEMENTAR LA CUENTA DE ELEMENTOS DENTRO DE UNA LISTA ...................................................... - 14 AGREGAR Y EXTRAER INDICANDO LA POSICIN .................................................................................... - 15 AGREGAR ANTES Y DESPUS DE UNA POSICIN DADA ........................................................................................ - 15 EXTRAER INDICANDO LA POSICIN ............................................................................................................... - 17 CDIGO PARA PROBAR AGREGAR Y EXTRAER INDICANDO LA POSICIN .................................................................. - 18 PILAS Y COLAS IMPLEMENTADAS CON LISTAS........................................................................................ - 20 PILA IMPLEMENTADA CON LISTA .................................................................................................................. - 20 COLA IMPLEMENTADA CON LISTA ................................................................................................................ - 22 LISTA ORDENADA................................................................................................................................... - 24 LA INTERFACE ICOMPARABLE ..................................................................................................................... - 24 IMPLEMENTACIN DE LA LISTA ORDENADA ..................................................................................................... - 26 CONCLUSIN. ........................................................................................................................................ - 28 -

Estructura de Datos y Listas Encadenadas Avanzadas

Introduccin
En esta publicacin voy a completar los conceptos sobre listas encadenadas y veremos algunas implementaciones, que se pueden realizar gracias a las capacidades que los lenguajes orientados a objetos de hoy en da brindan.

Listas encadenadas de doble enlace


Como se coment en la publicacin sobre listas enlazadas (las que podemos denominar simples), que solucionan la implementacin de bastante comportamiento de las situaciones reales que pueden sistematizarse en ambientes computarizados; sin embargo hay problemas de performance que en principio se solucionan con una implementacin que cuenta en su estructura interna con enlaces a la cabeza (head) y cola (tail) de la lista, pero a la hora de extraer el ltimo elemento de la lista, los problemas persisten. Una solucin es implementar la estructura interna del nodo de manera que presente enlaces al siguiente nodo y tambin al nodo anterior, esto complica el cdigo (se debe ser muy cuidadoso en su implementacin) pero brinda un nivel aceptable de versatilidad que puede ser exigencia en ciertas aplicaciones. Esquema y declaracin de la clase correspondiente al nuevo nodo

El grfico muestra el esquema para el nodo de una lista encadenada de doble enlace:

La declaracin para este tipo de nodo ahora cuenta con ms elementos en la estructura interna y constructores especializados que permiten construir un nuevo nodo y fijar los valores de cada campo de la estructura interna de acuerdo a las necesidades del desarrollador

-1-

Estructura de Datos y Listas Encadenadas Avanzadas

Cdigo fuente de Node.cs


namespace DemoLista3 { /// <summary> /// Clase para representar un nodo de listas doblemente enlazadas /// </summary> /// <typeparam name="ELEMENT">Tipo de dato del elemento</typeparam> internal class Node<ELEMENT> { #region Estructura Interna /// <summary> /// Mantiene el elemento en el nodo /// </summary> private ELEMENT item; /// <summary> /// Mantiene el enlace al siguiente nodo /// </summary> private Node<ELEMENT> next; /// <summary> /// Mantiene el elace al nodo anterior /// </summary> private Node<ELEMENT> prev; #endregion #region Constructores /// <summary> /// Constructor por defecto, inicializa los campos de la estructura /// interna en valores por defecto /// </summary> public Node() { this.Item = default(ELEMENT); this.Next = null; this.Prev = null; } /// <summary> /// Constructor especializado, permite fijar el elemento del nodo /// </summary> /// <param name="item">Elemento en el nodo</param> public Node(ELEMENT item) : this() { this.Item = item; } /// <summary> /// Constructor especializado, permite fijar el elemento del nodo /// y el valor del enlace al siguiente nodo /// </summary> /// <param name="item">Elemento en el nodo</param> /// <param name="next">Enlace al siguiente nodo</param> public Node(ELEMENT item, Node<ELEMENT> next) : this() { this.Item = item;

-2-

Estructura de Datos y Listas Encadenadas Avanzadas


this.Next = next; } /// <summary> /// Constructor especializado, permite fijar el elemento del nodo /// y el valor de ambos enlaces del nodo /// </summary> /// <param name="item">Elemento en el nodo</param> /// <param name="next">Enlace al siguiente nodo</param> /// <param name="prev">Enlace al nodo anterior</param> public Node(ELEMENT item, Node<ELEMENT> next, Node<ELEMENT> prev) : this() { this.Item = item; this.Next = next; this.Prev = prev; } /// <summary> /// Constructor especializado, permite fijar el enlace al /// siguiente nodo /// </summary> /// <param name="next">Enlace al siguiente nodo</param> public Node(Node<ELEMENT> next) : this() { this.Next = next; } /// <summary> /// Constructor especializado, permite fijar el enlace al /// siguiente nodo y al nodo anterior /// </summary> /// <param name="next">Enlace al siguiente nodo</param> /// <param name="prev">Enlace al nodo anterior</param> public Node(Node<ELEMENT> next, Node<ELEMENT> prev) : this() { this.Next = next; this.Prev = prev; } #endregion #region Propiedades Getters and Setters /// <summary> /// Facililta el acceso al elemento en el nodo /// </summary> public ELEMENT Item { get { return this.item; } set { this.item = value; } } /// <summary> /// Facilita el acceso al enlace al siguiente nodo

-3-

Estructura de Datos y Listas Encadenadas Avanzadas


/// </summary> public Node<ELEMENT> Next { get { return this.next; } set { this.next = value; } } /// <summary> /// Facilita el acceso al enlace al nodo anterior /// </summary> public Node<ELEMENT> Prev { get { return this.prev; } set { this.prev = value; } #endregion } } }

Cdigo fuente de List.cs para listas encadenadas de doble enlace


En el siguiente cdigo se muestra la implementacin de una lista encadenada de doble enlace, se basa en el cdigo realizado para lista encadenada (simple) y solamente se hicieron las consideraciones necesarias en los mtodos que implementan el comportamiento para agregar y extraer elementos. Es importante observar que estos cambios no afectan a los programas que utilizan objetos del tipo lista, justamente se es uno de los principios de la programacin orientada a objetos.
using System; namespace DemoLista3 { /// <summary> /// Implementacin de lista enlazada con dos puntos de acceso /// </summary> /// <typeparam name="ELEMENT">Tipo de dato de elementos que se introduce en la lista</typeparam> public class List <ELEMENT> { #region Estructura interna /// <summary>

-4-

Estructura de Datos y Listas Encadenadas Avanzadas


/// Mantiene un punto de entrada a la cabeza de lista /// </summary> private Node<ELEMENT> head; /// <summary> /// Mantiene un punto de entrada al ltimo nodo de la lista /// </summary> private Node<ELEMENT> tail; #endregion #region Constructores /// <summary> /// Constructor por defecto /// </summary> public List() { this.head = null; this.tail = null; } #endregion #region Propiedades /// <summary> /// Devuelve verdadero cuando la lista est vaca /// </summary> public bool IsEmpty { get { return this.head == null; } } #endregion #region Comportamiento de la lista enlazada /// <summary> /// Agrega un elemento al principio de la lista /// </summary> /// <param name="item">Elemento a agregar</param> public void AddToHead(ELEMENT item) { if (head == null) { head = new Node<ELEMENT>(item, null, null); tail = head; } else { Node<ELEMENT> temp = new Node<ELEMENT>(item, head, null); head.Prev = temp; // Ajusta el enlace hacia atras head = temp; } } /// <summary>

-5-

Estructura de Datos y Listas Encadenadas Avanzadas


/// Agrega un elemento al final de la lista /// </summary> /// <param name="item">Elemento a agregar</param> public void AddToTail(ELEMENT item) { if (head == null) { head = new Node<ELEMENT>(item, null, null); tail = head; } else { tail.Next = new Node<ELEMENT>(item, null, tail); tail = tail.Next; } } /// <summary> /// Determina si un elemento determinado se encuentra en la lista /// </summary> /// <param name="item">Elemento a buscar</param> /// <returns>Verdadero si el elemento se encuentra en la lista</returns> public bool Contains(ELEMENT item) { for (Node<ELEMENT> temp = head; temp != null; temp = temp.Next) { if (temp.Item.Equals(item)) { return true; } } return false; } /// <summary> /// Retira el elemento del principio de la lista /// </summary> /// <returns>Elemento retirado</returns> public ELEMENT RemoveFromHead() { if (IsEmpty) { throw new Exception("Lista vaca"); } Node<ELEMENT> temp = head; head = head.Next; // Avanza la posicin de head if (head == null) // Es el nico elemento en la lista? { tail = null; } else { head.Prev = null; // Ya no hay nodo anterior } temp.Next = null; // Asegurarse de anular la referencia return temp.Item; // Devolver el elemento no el nodo }

-6-

Estructura de Datos y Listas Encadenadas Avanzadas


/// <summary> /// Retira el elemento al final de la lista /// </summary> /// <returns>Elemento retirado</returns> public ELEMENT RemoveFromTail() { if (IsEmpty) { throw new Exception("Lista vaca"); } Node<ELEMENT> temp = tail; if (temp.Prev == null) // Es el nico { head = null; tail = null; } else { tail = tail.Prev; // Retroceder la cola de la lista tail.Next = null; // Ajusta el enlace al ltimo nodo temp.Prev = null; // Cancelar el enlace al anterior (por las dudas) } return temp.Item; } #endregion #region Mtodos heredados de object /// <summary> /// Representacin del contenido de la lista /// </summary> /// <returns>Cadena con el contenido de la lista</returns> public override string ToString() { string s = ""; for (Node<ELEMENT> temp = head; temp != null; temp = temp.Next) { s += temp.Item.ToString() + "\n"; } return s; } #endregion } }

Los comentarios incluidos en el cdigo son ms que explcitos de las situaciones que deben considerarse. Nota: El cdigo que se est mostrando puede y debe ser optimizado, es decir minimizar el consumo de recursos (variables) mejorar la ejecucin y los bucles de control. En este momento se est implementando soluciones que puedan ser entendidas por programadores "novatos", adems es importante seguir ciertas recomendaciones como las realizadas por Bruce Eckel en su obra "Thiking in C++":

-7-

Estructura de Datos y Listas Encadenadas Avanzadas First make it work, then make it fast. This is true even if you are certain that a piece of code is really important and that it will be a principal bottleneck in your system. Dont do it. Get the system going first with as simple a design as possible. Then if it isnt going fast enough, profile it. Youll almost always discover that your bottleneck isnt the problem. Save your time for the really important stuff. Que ms o menos dice: "En primer lugar hacer que funcione, luego que sea rpido. Esto es cierto incluso para piezas de cdigo realmente importantes que pueden ser el cuello de botella principal del sistema. Primero hay que lograr el sistema estable con un diseo tan simple como se pueda, entonces se mejora la velocidad (performance). Casi siempre se descubre que "su" cuello de botella (la del desarrollador) no es el cuello de botella del problema. Ahorre tiempo para las cosas realmente importantes." No hay que confundir estas palabras (y su traduccin) con lo que a veces ocurre, "hacer que funcione como sea". Cuidado las herramienta, tcnicas y mtodos ms apropiados son los que deben utilizarse yo nunca vi que un mecnico de autos desarme un motor con un tijera, siempre utiliza las herramientas que corresponden.; de la misma manera los desarrolladores de productos de software deben utilizar las herramientas y tcnicas que correspondan. Para probar el cdigo anterior, se puede y debe utilizar el mismo cdigo que en las implementaciones anteriores, de ese modo se est seguro que los cambios en el interior de los objetos del tipo lista siguen cumpliendo con el comportamiento esperado, solo que ahora lo harn ms rpido.

Implementar el recorrido de una lista mediante enumeradores


Desde hace un tiempo, los lenguajes orientados a objetos implementan un mecanismo para acceder uno tras otro a los objetos que una coleccin contiene. Antes de continuar es importante indicar que de ninguna manera se puede permitir que desde "fuera" de un objeto se manipule su contenido, esto va en contra de los principios de la programacin orientada a objetos adems cuando se codifica de ese modo (porque el desarrollador puede hacerlo) se est construyendo productos de software que son altamente dependientes (acoplados) unos con otros lo que inevitablemente lleva a problemas en el mantenimiento y reusabilidad de los productos. Con esto en mente es que se implementa un mecanismo que permita acceder a los elementos que una coleccin contienen (en este caso la lista) de manera que nos entregue de a uno de por vez y a partir del principio de la coleccin. Tengamos en cuenta que esto se realiza desde fuera del objeto coleccin (lista) y sin saber cmo es su estructura interna, de manera que el cdigo para entregar los elementos de a uno de por vez debe estar implementado en la coleccin, dado que es dependiente de su estructura interna y modo de acceso; sin embargo desde fuera de los objetos del tipo coleccin (lista) se accede a cada uno de ellos sin saber cmo se hace para traerlos. Eso es un excelente ejemplo de encapsulamiento y polimorfismo. En C# se puede utilizar una sentencia "foreach( ... )" que permite acceder a cada elemento u objeto almacenado en una coleccin, por ejemplo se podra codificar lo siguiente:

-8-

Estructura de Datos y Listas Encadenadas Avanzadas


Console.WriteLine("Personas mayores de 18 pero no tan viejas ..."); foreach (Person p in lista) { if ((p.Age > 18) && (p.Age < 50)) { Console.WriteLine("Persona: {0}", p.ToString()); } }

En este caso, el cdigo recorre la lista obteniendo cada una de las personas que en ella se encuentran, las recibe en la variable "p" que es del tipo Person (la lista es un lista de Person) y para cada una de ellas controla la edad mostrando solamente aquellas que cumplen el criterio establecido, mayores de 18 y menores de 50 aos. Los otros lenguajes orientados a objetos tienen una sintaxis parecida pero logran el mismo objetivo, acceder a cada uno de los elementos de una coleccin. Esto es posible solamente en colecciones que implementan una interface, y en el caso de C# esta interface es "IEnumerable". En el paradigma orientado a objetos se dice que una interface es un contrato de comportamiento, esto significa que cuando una clase implementa una interface, se est comprometiendo a realizar (implementar) el cdigo necesario de manera que los objetos de esa clase entiendan y respondan al comportamiento indicado en la interface. A modo de repaso, el comportamiento se representa mediante la especificacin funcional de cada mensaje que realiza dicho comportamiento; de este modo una interface es una enumeracin de mensajes que deben implementarse (codificarse de alguna manera) en las clases. En C#, la interface IEnumerable obliga a implementar el mtodo GetEnumerator() que devuelve un objeto del tipo IEnumerator que es lo que se utiliza con el foreach( ... ). El siguiente cdigo muestra la declaracin de la interface.
using System.Runtime.InteropServices; namespace System.Collections { // Summary: // Exposes the enumerator, which supports a simple iteration over a non-generic // collection. [ComVisible(true)] [Guid("496B0ABE-CDEE-11d3-88E8-00902754C43A")] public interface IEnumerable { // Summary: // Returns an enumerator that iterates through a collection. // // Returns: // An System.Collections.IEnumerator object that can be used to iterate through // the collection. [DispId(-4)] IEnumerator GetEnumerator(); } }

-9-

Estructura de Datos y Listas Encadenadas Avanzadas Bien, espero que en los cursos de programacin orientada a objetos (esta materia es Estructura de Datos) profundicen estos conceptos. Con esta interface tenemos la solucin para recorrer la lista, de manera que vamos a indicar que la clase List implementa la interface IEnumerable (se compromete a realizar ese comportamiento), en el cdigo de List.cs se hace de la siguiente manera:
using System; using System.Collections; namespace DemoLista3 { /// <summary> /// Implementacin de lista enlazada con dos puntos de acceso /// </summary> /// <typeparam name="ELEMENT">Tipo de dato de elementos que se introduce en la lista</typeparam> public class List <ELEMENT> : IEnumerable {

Observen que a continuacin del encabezado de la clase se utiliza ":" para indicar que esta clase implementa ciertas interfaces (se puede implementar ms de una), ah es donde se pone "IEnumerable". Otros lenguajes como Java por ejemplo utilizan la palabra reservada "implements" que al final es lo mismo. Una convencin es que los nombres de las interfaces comiencen con "I" (i mayscula) para no confundirse con la declaracin de clases heredadas o derivadas que se hace de igual forma. En esto Java es ms explicito porque utiliza la palabra reservada "inherits". Con esto la clase List se ha comprometido a implementar la interface IEnumerable, si se trata de compilar el cdigo no se podr hacer porque todava no se escribi el cdigo correspondiente al contrato que asumi la clase List. Para saber que mensajes / mtodos deben implementarse, hay que buscar un manual que nos diga todo lo que esa interface requiere. Por suerte los entornos integrados de desarrollo (IDE) nos ayudan, en el caso de Visual Studio basta con apuntar al nombre de la interface y en el men contextual seleccionar que se implemente la interface (Implements Interface), cuando se hace esto al final de la declaracin de la clase aparece lo siguiente:
#region IEnumerable Members IEnumerator IEnumerable.GetEnumerator() { throw new NotImplementedException(); } #endregion

Ahora est el mtodo para cumplir con el contrato, pero dispara una excepcin porque es responsabilidad del desarrollador escribir lo que haga falta para lograr ese comportamiento. En la lista, hace falta cdigo que vaya entregando uno a uno los elementos que estn dentro de la lista. Para eso se codifica un mtodo "private" porque desde fuera de la clase no hace falta invocarlo, en el cul recorremos la lista y entregamos uno a uno los elementos. Esto se hace as:

- 10 -

Estructura de Datos y Listas Encadenadas Avanzadas


#region IEnumerable Members /// <summary> /// Implementa el contrato indicado en IEnumerable /// </summary> /// <returns>Un objeto IEnumerator que permite recorrer la lista</returns> IEnumerator IEnumerable.GetEnumerator() { return MyEnumerator(); } /// <summary> /// Implementa un IEnumerator especfico para recorrer la lista. /// </summary> /// <returns>Objeto del tipo IEnumerator que recorre la lista</returns> private IEnumerator MyEnumerator() { for (Node<ELEMENT> skip = head; skip != null; skip = skip.Next) { yield return skip.Item; } } #endregion

Se utiliza la palabra reservada "yield" para indicar que es un return (parcial), caso contrario finalizara la ejecucin del bucle for ( ... ), lo que se indica es que la prxima vez deber continuar con el siguiente elemento.

Cdigo fuente para probar la implementacin


using System; namespace DemoLista3 { class Program { static void Main(string[] args) { List<Person> lista = new List<Person>(); lista.AddToHead(new Person("Juan", "Perez", new DateTime(1983, 1, 1))); Console.WriteLine("Lista Continene:\n{0}", lista.ToString()); Console.WriteLine("Se agrega a alguien por la cabeza de la lista"); lista.AddToHead(new Person("Carlos", "Rodriguez", new DateTime(1993, 10, 1))); Console.WriteLine("Lista Continene:\n{0}", lista.ToString()); Console.WriteLine("Se agrega a alguien por la cola de la lista"); lista.AddToTail(new Person("Mara", "Martinez", new DateTime(1990, 7, 1))); Console.WriteLine("Lista Continene:\n{0}", lista.ToString());

- 11 -

Estructura de Datos y Listas Encadenadas Avanzadas


Console.WriteLine("Se agrega por la cabeza y luego por la cola de la lista"); lista.AddToHead(new Person("Carla", "Andrada", new DateTime(1982, 1, 1))); lista.AddToTail(new Person("Marcos", "Gimenez", new DateTime(1945, 12, 1))); Console.WriteLine("Lista Continene:\n{0}", lista.ToString()); Console.WriteLine("Personas mayores de 18 pero no tan viejas ..."); foreach (Person p in lista) { if ((p.Age > 18) && (p.Age < 50)) { Console.WriteLine("Persona: {0}", p.ToString()); } } } } }

Se utiliza un constructor especializado para los objetos del tipo Person que permite indicar la fecha (ao, mes, da) de nacimiento y la propiedad Age de la clase Person se implementa as:
/// <summary> /// Facilita el acceo a la edad de la persona en aos /// </summary> public int Age { get { return ((DateTime.Today - this.birthDate)).Days / 365; } }

El siguiente diagrama muestra la relacin entre la clase e interface

- 12 -

Estructura de Datos y Listas Encadenadas Avanzadas

Implementar ndices en una lista


Al comenzar con estas publicaciones dije que una lista es un conjunto de elementos de manera que uno est despus de otro. Dependiendo de cmo se ubiquen en la memoria las listas pueden ser almacenadas en secuencia o encadenadas, pero al fin de cuentas un elemento siempre est despus de otro. Es razonable pensar que se puede acceder a los elementos de la lista mediante un ndice como si fuese un simple arreglo. Como somos programadores de primera lnea, vamos a utilizar otra de las capacidades que los lenguajes de programacin orientada a objetos nos brindan, en este caso la conocida como "Indexers" o sea acceder a elementos repetidos de un objeto mediante ndices (que por ahora van a ser numricos, pero pueden ser de cualquier tipo). Dado un objeto de cualquier tipo, se puede referenciar al mismo objeto mediante la palabra reservada "this", es por eso que se puede implementar el siguiente cdigo:
/// <summary> /// Implementa el acceso a los elementos de la lista mediante /// un ndice como si fuese un arreglo /// </summary> /// <param name="pos">Posicin (base cero) dentro de la lista que se desea acceder</param> /// <returns>Objeto del tipo ELEMENT en la posicin pos</returns> public ELEMENT this[int pos] { get { if (pos < 0) { throw new ArgumentException("Argumento invlido pos = " + pos.ToString()); } Node<ELEMENT> skip = head; for (int i = 0; i < pos; ++i) { skip = skip.Next; } if (skip == null) { throw new ArgumentException("Argumento invlido pos = {0} mayor que la cantidad de elementos", pos.ToString()); } return skip.Item; } }

Se trata de una propiedad que permite acceder a los elementos que se encuentran en la lista utilizando un ndice del tipo entero. Observen que este ndice no puede ser menor que cero y tampoco puede superar la cantidad de elementos que en la lista hay (lo cual no se sabe). El cdigo simplemente controla esta situacin y recorre la lista hasta llegar a la posicin indicada como parmetro, si todo marcha bien devuelve el elemento (Item) que en ese nodo est. Con esta propiedad implementada se puede escribir el siguiente cdigo de prueba:

- 13 -

Estructura de Datos y Listas Encadenadas Avanzadas


Console.WriteLine("Persona en la posicin 2: {0}", lista[1]); Console.WriteLine("Persona en la posicin 4: {0}", lista[3]);

Que obviamente mostrar los datos de la persona que se encuentre en la posicin indicada (siempre que est dentro de los lmites permitidos >= cero < cantidad de elementos dentro de la lista). Llego el momento para marcar lo importante que es acceder a los elementos de una coleccin mediante sentencias del tipo foreach( ... ), dado que no sabemos cmo estn almacenadas dentro de la coleccin y acabamos de ver que cuando se accede mediante ndices, cada vez que se accede se debe comenzar a "buscar" la posicin correcta.

Implementar la cuenta de elementos dentro de una lista


Si bien en el detalle del comportamiento de una lista no se indica que debe informar la cantidad de elementos que hay en ella, nos damos cuenta que es importante conocer ese valor y es posible incorporar un elemento a la estructura interna para mantener la cuenta de elementos en la lista que se debe incrementar cada vez que se agrega y decrementar cuando se extrae. La siguiente es una alternativa, en donde se utiliza un entero para mantener la cuenta de elementos dentro de la lista. Dicho valor se debe inicializar al momento de construir un objeto del tipo lista como se muestra en los siguientes fragmentos de cdigo:
/// <summary> /// Mantiene la cantidad de elementos dentro de la lista /// </summary> private int count; /// <summary> /// Constructor por defecto /// </summary> public List() { this.count = 0; this.head = null; this.tail = null; }

La siguiente cdigo muestra la implementacin de la propiedad para acceder al componente de la estructura interna que lleva la cuenta de elementos dentro de la lista. Para fijar el valor se califica como "protected", para que de ninguna manera se permita que desde fuera de la clase se cambie dicho valor, sin embargo desde la clase incluso desde clases derivadas si puede hacerse.
/// <summary> /// Devuelve la cantidad de elementos dentro de la lista /// </summary> public int Count { get { return this.count; }

- 14 -

Estructura de Datos y Listas Encadenadas Avanzadas


protected set { this.count = value; } }

Parece innecesario utilizar este mecanismo dado que se podra calificar como "protected" directamente el componente de la estructura interna "count" (con minsculas) pero lo que se intenta es realizar una implementacin con doble encapsulamiento. Esto significa que los propios mtodos de una clase acceden a la estructura por medio de las propiedades (getters y setters) de manera que facilite la posible modificacin de dicha estructura. En el ejemplo se hace solo con la propiedad Count (cuenta de elementos dentro de la lista) pero podra hacerse con los otros componentes de la estructura interna. Indudablemente esto implica un mayor consumo de tiempo de procesamiento dado que para acceder a cada elemento de la estructura interna se debe ejecutar cdigo (el de la propiedad) a diferencia de un acceso directo. La decisin de implementar o no doble encapsulamiento depende si la clase en cuestin va a ser derivada (heredada) por muchas otras y en ellas ser necesario manipular la estructura interna o no; de todos modos esa decisin no le corresponde al desarrollador sino al arquitecto de software y diseadores que intervienen en el proyecto de desarrollo de software.

Agregar y extraer indicando la posicin


Ahora que es posible determina el lugar donde se encuentra un elemento dentro de la lista, se puede implementar comportamiento para agregar y extraer elementos de la lista indicando en qu lugar se desea hacerlo.

Agregar antes y despus de una posicin dada


El siguiente cdigo implementa los mensajes void AddAfter(ELEMENT item, int pos) y void AddBefor(ELEMENT item, int pos) , que sirven justamente para agregar un elemento "item" antes o despus de la posicin indicada. La posicin puede hallarse mediante algn mecanismo de recorrido y test de valores.
/// <summary> /// Agrega un elemento despus de la posicin indicada /// </summary> /// <param name="item">Elemento a agregar</param> /// <param name="pos">Posicin a partir de la que se debe agregar</param> public void AddAfter(ELEMENT item, int pos) { if (IsEmpty || (pos < 0)) // esta vaca o la posicin es negativa { AddToHead(item); } else { Node<ELEMENT> skip = head; for (int i = 0; (skip != null) && (i < pos); ++i) { skip = skip.Next; }

- 15 -

Estructura de Datos y Listas Encadenadas Avanzadas


if (skip == null) // llego al final { AddToTail(item); // TODO: Confirmar con el cliente } else { Node<ELEMENT> temp = new Node<ELEMENT>(item); if (skip.Next != null) // hay algo despues { temp.Next = skip.Next; skip.Next.Prev = temp; } skip.Next = temp; temp.Prev = skip; ++Count; } } } /// <summary> /// Agrega un elemento despus de la posicin indicada /// </summary> /// <param name="item">Elemento a agregar</param> /// <param name="pos">Posicin a partir de la que se debe agregar</param> public void AddBefore(ELEMENT item, int pos) { if (IsEmpty || (pos < 1)) { AddToHead(item); } else { Node<ELEMENT> skip = head; for (int i = 0; (skip != null) && (i < pos); ++i) { skip = skip.Next; } if (skip == null) // llego al final { AddToTail(item); } else { Node<ELEMENT> temp = new Node<ELEMENT>(item); if (skip.Prev != null) // hay algo adelante { temp.Prev = skip.Prev; skip.Prev.Next = temp; } skip.Prev = temp; temp.Next = skip; ++Count; } } }

- 16 -

Estructura de Datos y Listas Encadenadas Avanzadas El anlisis del cdigo muestra que siempre que se posible se debe utilizar comportamiento ya implementado y probado, de manera que solo se programa los ajustes de enlaces necesarios.

Extraer indicando la posicin


El siguiente cdigo implementa el cdigo necesario para el mensaje ELEMENT RemoveAt(int pos), que extrae el elemento que se encuentra en la posicin indicada como parmetro.
/// <summary> /// Extrae el elemento que se encuentra en la posicin indicada /// </summary> /// <param name="pos">Posicin donde se desea extraer el elemento</param> /// <returns>Objeto del tipo ELEMENT que se extrae</returns> public ELEMENT RemoveAt(int pos) { if (IsEmpty || (pos < 0) || (pos > Count)) { throw new ArgumentException("Argumento fuera de los lmites de la lista pos = " + pos.ToString()); } Node<ELEMENT> skip = head; for (int i = 0; (skip != null) && (i < pos); ++i) { skip = skip.Next; } if (skip == null) { throw new Exception("Error inesperado en la posicin pos = " + pos.ToString()); } if (skip.Prev == null) // es el primero { if (skip.Next == null) // es el primero y ltimo { head = tail = null; // vaciar la lista } else { head = head.Next; // ajustar la cabeza head.Prev = null; skip.Next = null; // cancelar la referencia } } else { if (skip.Next == null) // es el ltimo { tail = tail.Prev; // ajustar la cola tail.Next = null; skip.Prev = null; // cancelar la referencia } else { // estamos en el medio de la lista skip.Prev.Next = skip.Next; // ajustar los enlaces

- 17 -

Estructura de Datos y Listas Encadenadas Avanzadas


skip.Next.Prev = skip.Prev; skip.Next = skip.Prev = null; // cancelar las referencias } } --Count; return skip.Item; }

En esta oportunidad se ha codificado todas las alternativas, pero en los casos que corresponda se puede invocar los mtodos RemoveFromHead() o RemoveFromTail() que ya estn probados. De todos modos sirve como ejemplo.

Cdigo para probar Agregar y Extraer indicando la posicin


using System; namespace DemoLista3 { class Program { static void Main(string[] args) { List<string> cadenas = new List<string>(); cadenas.AddToTail("hola"); cadenas.AddToTail("como"); cadenas.AddToTail("ests"); Console.WriteLine("cadenas:\n{0}", cadenas.ToString()); int pos = cadenas.Find("como"); Console.WriteLine("La cadena \"como\" est en la posicin {0}", pos); cadenas.AddAfter("Pedro", 0); Console.WriteLine("cadenas:\n{0}", cadenas.ToString()); cadenas.AddAfter("Bien ...", 3); Console.WriteLine("cadenas:\n{0}", cadenas.ToString()); cadenas.AddBefore("Espero", 4); Console.WriteLine("cadenas:\n{0}", cadenas.ToString()); cadenas.AddBefore("que", cadenas.Count-1); Console.WriteLine("cadenas:\n{0}", cadenas.ToString()); cadenas.AddBefore("Saludo completo", 0); Console.WriteLine("cadenas:\n{0}", cadenas.ToString()); cadenas.RemoveAt(1); Console.WriteLine("cadenas:\n{0}", cadenas.ToString()); } } }

Observen que en este caso se utiliza una lista de cadenas (string) dado que las saben compararse unas con otras, recordemos que los mtodos bool Contains(ELEMENT item) y int Find(ELEMENT

- 18 -

Estructura de Datos y Listas Encadenadas Avanzadas item), necesitan que los objetos del tipo ELEMENT entiendan el mtodo Equals(...), lo que no ocurre con los objetos del tipo Person. La salida de esta prueba es la siguiente:

Observaciones: El cdigo que se ha realizado hasta ahora en algunos casos es aplicable a lista (simple) aquella en la que solo se tiene un enlace al siguiente elemento de la lista y en otros casos no lo es; sin embargo se puede modificar para que lo sea.

- 19 -

Estructura de Datos y Listas Encadenadas Avanzadas

Pilas y colas implementadas con listas


Una de las posibilidades que brinda la lista enlazada es que con ella se puede implementar el comportamiento de listas de acceso restringido como son la pila (Stack) o la cola (Queue).

Pila implementada con lista


Para implementar una pila basta con utilizar una lista en la estructura interna de la misma y utilizar los mtodos que permiten agregar y extraer por la cabeza de la lista; de ese modo se implementa el comportamiento LIFO que las pilas tienen. El siguiente cdigo muestra cmo se puede hacer:
using System; namespace DemoLista3 { /// <summary> /// Implementacin de Lista de pila almacenada en secuencia /// </summary> public class Stack<ELEMENT> { #region Estructura Interna /// <summary> /// Mantiene la coleccin de elementos /// </summary> private List<ELEMENT> collection; #endregion #region Constructores /// <summary> /// Constructor por defecto /// Nos aseguramos que el contenedor sea vlido /// </summary> public Stack() { this.collection = new List<ELEMENT>(); } #endregion #region Propiedades /// <summary> /// Determina si la pila (Stack) est vaca /// </summary> public bool IsEmpty { get { return this.collection.IsEmpty; } } /// <summary> /// Determina si la pila (Stack) est llena /// </summary> public bool IsFull {

- 20 -

Estructura de Datos y Listas Encadenadas Avanzadas


get { return false; } } /// <summary> /// Determina si la pila (Stack) est normal /// </summary> public bool IsNormal { get { return !this.IsEmpty; } } #endregion #region Mtodos que implementan el comportamiento /// <summary> /// Agrega un elemento en la pila (Stack) /// <precondition> /// La pila (Stack) no debe estar llena /// </precondition> /// </summary> /// <param name="x">Elemento que se agrega</param> public void Push(ELEMENT x) { this.collection.AddToHead(x); } /// <summary> /// Extrae un elemento de la pila (Stack) /// <precondition> /// La pila (Stack) debe contener elementos /// </precondition> /// </summary> /// <returns>Elemento extrado</returns> public ELEMENT Pop() { if (this.IsEmpty) { throw new Exception("Intento de sacar un elemento de una pila vaca"); } return this.collection.RemoveFromHead(); } /// <summary> /// Deveuelve el elemento que puede extraerse de la pila (Stack) sin sacarlo /// <precondition> /// La pila (Stack) debe contener elementos /// </precondition> /// </summary> /// <returns>Elemento que puede extraerse</returns> public ELEMENT Peek() { if (this.IsEmpty) {

- 21 -

Estructura de Datos y Listas Encadenadas Avanzadas


throw new Exception("Intento de ver un elemento de una pila vaca"); } return this.collection[0]; } #endregion } }

Cola implementada con lista


Una cola implementada con un lista tambin mantienen en su estructura interna un objeto del tipo lista y el comportamiento FIFO se logra agregando por final y extrayendo por la cabeza de la lista. El siguiente cdigo muestra cmo se puede hacer:
using System; namespace DemoLista3 { /// <summary> /// Implementacin de Lista de cola almacenada en secuencia /// </summary> public class Queue <ELEMENT> { #region Estructura Interna /// <summary> /// Mantiene la coleccin de elementos /// </summary> private List<ELEMENT> collection; #endregion #region Constructores /// <summary> /// Constructor por defecto /// Nos aseguramos que el contenedor sea vlido /// </summary> public Queue() { this.collection = new List<ELEMENT>(); } #endregion #region Propiedades /// <summary> /// Determina si la cola (Queue) est vaca /// </summary> public bool IsEmpty { get { return this.collection.IsEmpty; }

- 22 -

Estructura de Datos y Listas Encadenadas Avanzadas


} /// <summary> /// Determina si la cola (Queue) est llena /// </summary> public bool IsFull { get { return false; } } /// <summary> /// Determina si la cola (Queue) est normal /// </summary> public bool IsNormal { get { return !this.IsEmpty; } } #endregion #region Mtodos que implementan el comportamiento /// <summary> /// Agrega un elemento en la cola (Queue) /// <precondition> /// La cola (Queue) no debe estar llena /// </precondition> /// </summary> /// <param name="x">Elemento que se agrega</param> public void Enqueue(ELEMENT x) { this.collection.AddToTail(x); } /// <summary> /// Extrae un elemento de la cola (Queue) /// <precondition> /// La cola (Queue) debe contener elementos /// </precondition> /// </summary> /// <returns>Elemento extrado</returns> public ELEMENT Dequeue() { if (this.IsEmpty) { throw new Exception("Intento de sacar un elemento de una cola vaca"); } return this.collection.RemoveFromHead(); } /// <summary> /// Deveuelve el elemento que puede extraerse de la cola (Queue) sin sacarlo /// <precondition> /// La cola (Queue) debe contener elementos

- 23 -

Estructura de Datos y Listas Encadenadas Avanzadas


/// </precondition> /// </summary> /// <returns>Elemento que puede extraerse</returns> public ELEMENT Peek() { if (this.IsEmpty) { throw new Exception("Intento de consultar un elemento en una cola vaca"); } return this.collection[0]; } #endregion } }

En ambos casos, tanto la pila (Stack) como cola (Queue) no saben qu tipo de lista estn utilizando en su estructura interna, lo que es correcto desde el punto de vista del encapsulamiento; sin embargo por una cuestin de utilizar mejor los recursos (procesador y memoria) es conveniente que se implemente con listas encadenadas de un solo enlace en el nodo (las simples) y en el caso de la cola (Queue) es necesario contar con enlaces a la cabeza y cola de la lista.

Lista ordenada
Es bastante comn hallar en la vida real que los procesos implementan una especie de "turno" o "nmero" que indica el orden en que se realizaran las cosas; tal es el caso de como atienden algunos odontlogos o por ejemplo como se prioriza algn servicio de acuerdo a un valor. La situacin planteada se puede resolver con lo que se conoce como una lista ordenada, que no es otra cosa que una lista en la que los elementos se agregan respetando un orden determinado, de manera que siempre se acceda a la lista desde el principio hasta el fin se podr acceder a los elementos en dicho orden sin importar como fueron agregados a la lista. La cuestin es que un objeto del tipo lista sabe cmo tiene que mantener ordenados a los elementos, esto no es nada complicado dado que ya se cuenta con mtodos para agregar elementos donde sea necesario, al principio, al final y antes o despus de algn lugar en particular; el problema es que la lista no sabe cuando un elemento es mayor o menor que otro elemento. Es correcto, la lista no debe saber cuando un elemento es mayor o menor que otro elemento; si lo hiciera estara violando el encapsulamiento de los objetos del tipo ELEMENT adems como la implementacin es genrica nadie puede saber a priori cuando uno de esos elementos es mayor o menor que otro. Esta capacidad (comportamiento) saber cuando un objeto es mayor o menor que otro del mismo tipo debe implementarse en el objeto mismo y la lista debe utilizar algn mensaje que le indique cuando un objeto es mayor o menor que otro.

La interface IComparable
Claramente se ha establecido que hace falta un comportamiento que indique cuando un objeto es mayor, menor o igual que otro objeto obviamente del mismo tipo (no se compara peras con manzanas). Eso es un contrato de comportamiento y en C# se conoce como la interface IComparable.

- 24 -

Estructura de Datos y Listas Encadenadas Avanzadas A continuacin se tiene el contrato que debe implementarse:
using System.Runtime.InteropServices; namespace System { // Summary: // Defines a generalized type-specific comparison method that a value type or // class implements to order or sort its instances. [ComVisible(true)] public interface IComparable { // Summary: // Compares the current instance with another object of the same type and returns // an integer that indicates whether the current instance precedes, follows, // or occurs in the same position in the sort order as the other object. // // Parameters: // obj: // An object to compare with this instance. // // Returns: // A 32-bit signed integer that indicates the relative order of the objects // being compared. The return value has these meanings: Value Meaning Less than // zero This instance is less than obj. Zero This instance is equal to obj. // Greater than zero This instance is greater than obj. // // Exceptions: // System.ArgumentException: // obj is not the same type as this instance. int CompareTo(object obj); } }

Lo que nos dice es que para que un objeto de cualquier tipo pueda saber si es mayor, menor o igual que otro del mismo tipo, debe implementar un mtodo int CompareTo(object obj) que devuelve un nmero entero cuyo significado es el siguiente: mayor que cero el objeto que ejecuta el mensaje es mayor que el objeto parmetro. menor que cero el objeto que ejecuta el mensaje es menor que el objeto parmetro. igual a cero objeto que ejecuta el mensaje es igual que el objeto parmetro.

Entonces si queremos que los objetos del tipo Person sepan cmo compararse, debemos implementar la interface; para eso hay que indicarlo en la declaracin de la clase y luego pedirle al entorno que nos "escriba" lo que hace falta.
using System;

- 25 -

Estructura de Datos y Listas Encadenadas Avanzadas


namespace DemoLista3 { public class Person : IComparable {

El cdigo que efectivamente implementa la interface es el siguiente:


#region IComparable Members /// <summary> /// Implementacin de la interface IComparable para lograr que /// los objetos sepan cmo compararse entre ellos /// </summary> /// <param name="obj">Objeto con el cul se compara</param> /// <returns>Entero positivo si es mayor, negativo si es menor e igual a cero si son iguales</returns> int IComparable.CompareTo(object obj) { if ((obj != null) && (obj is Person)) { return CompareNames((Person)obj); } throw new Exception(String.Format("No se puede comparar con obj = {0}", (obj==null? "null" : obj.GetType().ToString()))); } /// <summary> /// Implementacin personalizada para comparar objetos del tipo Person /// </summary> /// <param name="other">Otro obejto del tipo Person</param> /// <returns>Entero positivo si es mayor, negativo si es menor e igual a cero si son iguales</returns> private int CompareNames(Person other) { return this.lastName.CompareTo(other.lastName); } #endregion

El cdigo anterior implementa la interface y lo hace con un mtodo (privado) que compara los apellidos obviamente utilizando el mtodo CompareTo que tienen los string. De esta manera se puede lograr una lista ordenada alfabticamente segn el apellido de cada persona.

Implementacin de la lista ordenada


Para que los objetos del tipo lista "exijan" que los objetos del tipo ELEMENT implementen la interface IComparable hace falta indicarlo en la declaracin de la clase de la siguiente manera:
/// <summary> /// Implementacin de lista enlazada con dos puntos de acceso /// </summary> /// <typeparam name="ELEMENT">Tipo de dato de elementos que se introduce en la lista</typeparam> public class List <ELEMENT> : IEnumerable where ELEMENT : IComparable {

Se est indicando que para poder compilar este cdigo los objetos del tipo ELEMENT deben implementar la interface IComparable, caso contrario no compila. - 26 -

Estructura de Datos y Listas Encadenadas Avanzadas De este modo es cmo el entorno integrado de desarrollo nos puede mostrar los mtodos que los objetos del tipo ELEMENT tienen y resulta que el IDE tampoco sabe qu tipo de objetos van a utilizarse, esto realmente es una excelente muestra del famoso encapsulamiento y polimorfismo que tanto se habla con los objetos. El cdigo para agregar en orden responde al siguiente mensaje void AddInOrder(ELEMENT item) , cuya implementacin se muestra a continuacin:
/// <summary> /// Agrega un elemento en la lista ordenada /// </summary> /// <param name="item">Elemento a agregar</param> public void AddInOrder(ELEMENT item) { if (IsEmpty || (item.CompareTo(head.Item) < 0)) { // item es menor que el primer elemento de la lista AddToHead(item); } else { Node<ELEMENT> skip = head; while ((skip != null) && (item.CompareTo(skip.Item) >= 0)) { skip = skip.Next; } if (skip == null) { // item es mayor que todos los elementos de la lista AddToTail(item); } else { // item es menor que el elemento que esta en el nodo skip // se debe agregar el nuevo nodo antes Node<ELEMENT> temp = new Node<ELEMENT>(item); temp.Prev = skip.Prev; skip.Prev.Next = temp; temp.Next = skip; skip.Prev = temp; ++Count; } } }

Para probar esto se puede utilizar el siguiente test:


using System; namespace DemoLista3 { class Program { static void Main(string[] args) {

- 27 -

Estructura de Datos y Listas Encadenadas Avanzadas


List<Person> lista = new List<Person>(); lista.AddInOrder(new Person("Juan", "Perez", new DateTime(1983, 1, 1))); Console.WriteLine("Lista Continene:\n{0}", lista.ToString()); lista.AddInOrder(new Person("Carlos", "Rodriguez", new DateTime(1993, 10, 1))); Console.WriteLine("Lista Continene:\n{0}", lista.ToString()); lista.AddInOrder(new Person("Mara", "Martinez", new DateTime(1990, 7, 1))); Console.WriteLine("Lista Continene:\n{0}", lista.ToString()); lista.AddInOrder(new Person("Carla", "Andrada", new DateTime(1982, 1, 1))); Console.WriteLine("Lista Continene:\n{0}", lista.ToString()); lista.AddInOrder(new Person("Marcos", "Gimenez", new DateTime(1945, 12, 1))); Console.WriteLine("Lista Continene:\n{0}", lista.ToString()); } } }

La salida es:

Conclusin.
Estos ejercicios son para aprender cmo se pueden hacer las cosas, siempre se debe revisar la librera de clases que el lenguaje de programacin nos brinda y seguramente hallaremos que existen todos estos tipos de estructuras de datos. Como informticos tenemos que saber hacerlas y utilizar las que nos dan hechas.

- 28 -

You might also like