43 - Estructuras dinámicas: Listas tipo Pila |
Una lista se comporta como una pila si las inserciones y extracciones las hacemos por un mismo lado de la lista. También se las llama listas LIFO (Last In First Out - último en entrar primero en salir)
Importante: Una pila al ser una lista puede almacenar en el campo de información cualquier tipo de valor (int, char, float, vector de caracteres, un objeto, etc)
Para estudiar el mecanismo de utilización de una pila supondremos que en el campo de información almacena un entero (para una fácil interpretación y codificación)
Inicialmente la PILA está vacía y decimos que el puntero raiz apunta a null (Si apunta a null decimos que no tiene una dirección de memoria):
Insertamos un valor entero en la pila: insertar(10)
Luego de realizar la inserción la lista tipo pila queda de esta manera: un nodo con el valor 10 y raiz apunta a dicho nodo. El puntero del nodo apunta a null ya que no hay otro nodo después de este.
Insertamos luego el valor 4: insertar(4)
Ahora el primer nodo de la pila es el que almacena el valor cuatro. raiz apunta a dicho nodo. Recordemos que raiz es el puntero externo a la lista que almacena la dirección del primer nodo. El nodo que acabamos de insertar en el campo puntero guarda la dirección del nodo que almacena el valor 10.
Ahora qué sucede si extraemos un nodo de la pila. ¿Cuál se extrae? Como sabemos en una pila se extrae el último en entrar.
Al extraer de la pila tenemos: extraer()
La pila ha quedado con un nodo.
Hay que tener cuidado que si se extrae un nuevo nodo la pila quedará vacía y no se podrá extraer otros valores (avisar que la pila está vacía)
Confeccionar una clase que administre una lista tipo pila (se debe poder insertar, extraer e imprimir los datos de la pila)
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ListasTipoPila1 { class Pila { class Nodo { public int info; public Nodo sig; } private Nodo raiz; public Pila() { raiz = null; } public void Insertar(int x) { Nodo nuevo; nuevo = new Nodo(); nuevo.info = x; if (raiz == null) { nuevo.sig = null; raiz = nuevo; } else { nuevo.sig = raiz; raiz = nuevo; } } public int Extraer() { if (raiz != null) { int informacion = raiz.info; raiz = raiz.sig; return informacion; } else { return int.MaxValue; } } public void Imprimir() { Nodo reco=raiz; Console.WriteLine("Listado de todos los elementos de la pila."); while (reco!=null) { Console.Write(reco.info+"-"); reco=reco.sig; } Console.WriteLine(); } static void Main(string[] args) { Pila pila1=new Pila(); pila1.Insertar(10); pila1.Insertar(40); pila1.Insertar(3); pila1.Imprimir(); Console.WriteLine("Extraemos de la pila:"+pila1.Extraer()); pila1.Imprimir(); Console.ReadKey(); } } }
Analicemos las distintas partes de este programa:
class Nodo { public int info; public Nodo sig; } private Nodo raiz;
Para declarar un nodo debemos utilizar una clase. En este caso la información del nodo (info) es un entero y siempre el nodo tendrá una referencia de tipo Nodo, que le llamamos sig.
El puntero sig apunta al siguiente nodo o a null en caso que no exista otro nodo. Este puntero es interno a la lista. Para poder acceder a los atributos los definimos de tipo public.
También definimos un puntero de tipo Nodo llamado raiz. Este puntero tiene la dirección del primer nodo de la lista. En caso de estar vacía la lista, raiz apunta a null (es decir no tiene dirección)
El puntero raiz es fundamental porque al tener la dirección del primer nodo de la lista nos permite acceder a los demás nodos.
public Pila() { raiz = null; }
En el constructor de la clase hacemos que raiz guarde el valor null. Tengamos en cuenta que si raiz tiene almacenado null la lista está vacía, en caso contrario tiene la dirección del primer nodo de la lista.
public void Insertar(int x) { Nodo nuevo; nuevo = new Nodo(); nuevo.info = x; if (raiz == null) { nuevo.sig = null; raiz = nuevo; } else { nuevo.sig = raiz; raiz = nuevo; } }
Uno de los métodos más importantes que debemos entender en una pila es el de Insertar un elemento en la pila.
Al método llega la información a insertar, en este caso en particular es un valor entero.
La creación de un nodo requiere dos pasos:
- Definición de un puntero o referencia a un tipo de dato Nodo:
Nodo nuevo;- Creación del nodo (creación de un objeto):
nuevo = new Nodo();
Cuando se ejecuta el operador new se reserva espacio para el nodo. Realmente se crea el nodo cuando se ejecuta el new.
Paso seguido debemos guardar la información del nodo:
nuevo.info = x;
En el campo info almacenamos lo que llega en el parámetro x. Por ejemplo si llega un 5 el nodo queda:
Por último queda enlazar el nodo que acabamos de crear al principio de la lista.
Si la lista está vacía debemos guardar en el campo sig del nodo el valor null para indicar que no hay otro nodo después de este, y hacer que raiz apunte al nodo creado (sabemos si una lista esta vacía si raiz almacena un null)
if (raiz == null) { nuevo.sig = null; raiz = nuevo; }
Gráficamente podemos observar que cuando indicamos raiz=nuevo, el puntero raiz guarda la dirección del nodo apuntado por nuevo.
Tener en cuenta que cuando finaliza la ejecución del método el puntero nuevo desaparece, pero no el nodo creado con el operador new.
En caso que la lista no esté vacía, el puntero sig del nodo que acabamos de crear debe apuntar al que es hasta este momento el primer nodo, es decir al nodo que apunta raiz actualmente.
else { nuevo.sig = raiz; raiz = nuevo; }
Como primera actividad cargamos en el puntero sig del nodo apuntado por nuevo la dirección de raiz, y posteriormente raiz apunta al nodo que acabamos de crear, que será ahora el primero de la lista.
Antes de los enlaces tenemos:
Luego de ejecutar la línea:
nuevo.sig = raiz;
Ahora tenemos:
Por último asignamos a raiz la dirección que almacena el puntero nuevo.
raiz = nuevo;
La lista queda:
El método Extraer:
public int Extraer() { if (raiz != null) { int informacion = raiz.info; raiz = raiz.sig; return informacion; } else { return int.MaxValue; } }
El objetivo del método extraer es retornar la información del primer nodo y además borrarlo de la lista.
Si la lista no está vacía guardamos en una variable local la información del primer nodo:
int informacion = raiz.info;
Avanzamos raiz al segundo nodo de la lista, ya que borraremos el primero:
raiz = raiz.sig;
el nodo que previamente estaba apuntado por raiz es eliminado automáticamente, al no tener ninguna referencia.
Retornamos la información:
return informacion;
En caso de estar vacía la pila retornamos el número entero máximo y lo tomamos como código de error (es decir nunca debemos guardar el entero mayor en la pila)
return int.MaxValue;
Es muy importante entender gráficamente el manejo de las listas. La interpretación gráfica nos permitirá plantear inicialmente las soluciones para el manejo de listas.
Por último expliquemos el método para recorrer una lista en forma completa e imprimir la información de cada nodo:
public void Imprimir() { Nodo reco=raiz; Console.WriteLine("Listado de todos los elementos de la pila."); while (reco!=null) { Console.Write(reco.info+"-"); reco=reco.sig; } Console.WriteLine(); }
Definimos un puntero auxiliar reco y hacemos que apunte al primer nodo de la lista:
Nodo reco=raiz;
Disponemos una estructura repetitiva que se repetirá mientras reco sea distinto a null. Dentro de la estructura repetitiva hacemos que reco avance al siguiente nodo:
while (reco!=null) { Console.Write(reco.info+"-"); reco=reco.sig; }
Es muy importante entender la línea:
reco=reco.sig;
Estamos diciendo que reco almacena la dirección que tiene el puntero sig del nodo apuntado actualmente por reco.
Gráficamente:
Al analizarse la condición:
while (reco!=null)
se valúa en verdadero ya que reco apunta a un nodo y se vuelve a ejecutar la línea:
reco=reco.sig;
Ahora reco apunta al siguiente nodo:
La condición del while nuevamente se valúa en verdadera y avanza el puntero reco al siguiente nodo:
reco=reco.sig;
Ahora sí reco apunta a null y ha llegado el final de la lista (Recordar que el último nodo de la lista tiene almacenado en el puntero sig el valor null, con el objetivo de saber que es el último nodo)
Para poder probar esta clase recordemos que debemos definir un objeto de la misma y llamar a sus métodos:
static void Main(string[] args) { Pila pila1=new Pila(); pila1.Insertar(10); pila1.Insertar(40); pila1.Insertar(3); pila1.Imprimir(); Console.WriteLine("Extraemos de la pila:"+pila1.Extraer()); pila1.Imprimir(); Console.ReadKey(); }
Insertamos 3 enteros, luego imprimimos la pila, extraemos uno de la pila y finalmente imprimimos nuevamente la pila.
Agregar a la clase Pila un método que retorne la cantidad de nodos y otro que indique si esta vacía.
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ListasTipoPila1 { class Pila { class Nodo { public int info; public Nodo sig; } private Nodo raiz; public Pila() { raiz = null; } public void Insertar(int x) { Nodo nuevo; nuevo = new Nodo(); nuevo.info = x; if (raiz == null) { nuevo.sig = null; raiz = nuevo; } else { nuevo.sig = raiz; raiz = nuevo; } } public int Extraer() { if (raiz != null) { int informacion = raiz.info; raiz = raiz.sig; return informacion; } else { return int.MaxValue; } } public void Imprimir() { Nodo reco=raiz; Console.WriteLine("Listado de todos los elementos de la pila."); while (reco!=null) { Console.Write(reco.info+"-"); reco=reco.sig; } Console.WriteLine(); } public bool Vacia() { if (raiz == null) { return true; } else { return false; } } public int Cantidad() { int cant = 0; Nodo reco = raiz; while (reco != null) { cant++; reco = reco.sig; } return cant; } static void Main(string[] args) { Pila pila1=new Pila(); pila1.Insertar(10); pila1.Insertar(40); pila1.Insertar(3); pila1.Imprimir(); Console.WriteLine("La cantidad de nodos de la lista es:"+pila1.Cantidad()); while (pila1.Vacia()==false) { Console.WriteLine(pila1.Extraer()); } Console.ReadKey(); } } }
Para verificar si la pila esta vacía verificamos el contenido de la variable raiz, si tiene null luego la lista esta vacía y por lo tanto retornamos un true:
public bool Vacia() { if (raiz == null) { return true; } else { return false; } }
El algoritmo para saber la cantidad de nodos es similar al imprimir, pero en lugar de mostrar la información del nodo procedemos a incrementar un contador:
public int Cantidad() { int cant = 0; Nodo reco = raiz; while (reco != null) { cant++; reco = reco.sig; } return cant; }
Para probar esta clase en la main creamos un objeto de la clase Pila insertamos tres enteros:
Pila pila1=new Pila(); pila1.Insertar(10); pila1.Insertar(40); pila1.Insertar(3);
Imprimimos la pila (nos muestra los tres datos):
pila1.Imprimir();
Llamamos al método Cantidad (nos retorna un 3):
Console.WriteLine("La cantidad de nodos de la lista es:"+pila1.Cantidad());
Luego mientras el método Vacia nos retorne un false (lista no vacía) procedemos a llamar al método extraer:
while (pila1.Vacia()==false) { Console.WriteLine(pila1.Extraer()); }
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ListasTipoPila1 { class Pila { class Nodo { public int info; public Nodo sig; } private Nodo raiz; public Pila() { raiz = null; } public void Insertar(int x) { Nodo nuevo; nuevo = new Nodo(); nuevo.info = x; if (raiz == null) { nuevo.sig = null; raiz = nuevo; } else { nuevo.sig = raiz; raiz = nuevo; } } public int Extraer() { if (raiz != null) { int informacion = raiz.info; raiz = raiz.sig; return informacion; } else { return int.MaxValue; } } public void Imprimir() { Nodo reco=raiz; Console.WriteLine("Listado de todos los elementos de la pila."); while (reco!=null) { Console.Write(reco.info+"-"); reco=reco.sig; } Console.WriteLine(); } public bool Vacia() { if (raiz == null) { return true; } else { return false; } } public int Cantidad() { int cant = 0; Nodo reco = raiz; while (reco != null) { cant++; reco = reco.sig; } return cant; } public int Retornar() { if (raiz != null) { int informacion = raiz.info; return informacion; } else { return int.MaxValue; } } static void Main(string[] args) { Pila pila1=new Pila(); pila1.Insertar(10); pila1.Insertar(40); pila1.Insertar(3); pila1.Imprimir(); Console.WriteLine("Extraemos de la pila:"+pila1.Extraer()); pila1.Imprimir(); Console.WriteLine("Retornamos primero de la pila:"+pila1.Retornar()); pila1.Imprimir(); Console.ReadKey(); } } }