Listas enlazadas – Clase Lista,Nodo en c++

4 07 2009

Una lista es una estructura de datos que nos permite agrupar elementos de una manera organizada. Las listas al igual que los algoritmos son importantísimas en la computación y críticas en muchos programas informáticos.

Las listas están compuestas por nodos, estos nodos tienen un dato o valor y un puntero a otro(s) nodo(s).

Existen varios tipos de listas: Simplemente enlazada, doblemente enlazada, circular simplemente enlazada, circular doblemente enlazada.

Vamos a revisar las listas enlazadas simples, por ser el punto de partida y fundamentales para poder entender las otras.

Una lista enlazada tiene un conjunto de nodos, los cuales almacenan 2 tipos de información: El dato que contienen y un puntero al siguiente nodo en la lista. El último nodo de la lista tiene como siguiente nodo el valor NULL. Entonces las listas enlazadas simples solo pueden ser recorridas en una dirección, apuntando al nodo siguiente, mas no a un nodo anterior.

Aquí una ejemplo de un lista enlazada simple.

En cristiano:
55-> 60-> 31-> 5-> 4-> 51-> 9-> 27-> 68-> 62-> NULL

Internamente:
Nodo-> Dato: 55 Direcion: 0x3d2c00 Siguiente: 0x3d2c80
Nodo-> Dato: 60 Direcion: 0x3d2c80 Siguiente: 0x3d2c90
Nodo-> Dato: 31 Direcion: 0x3d2c90 Siguiente: 0x3d2ca0
Nodo-> Dato: 5  Direcion: 0x3d2ca0 Siguiente: 0x3d2cb0
Nodo-> Dato: 4  Direcion: 0x3d2cb0 Siguiente: 0x3d2cc0
Nodo-> Dato: 51 Direcion: 0x3d2cc0 Siguiente: 0x3d3ab8
Nodo-> Dato: 9  Direcion: 0x3d3ab8 Siguiente: 0x3d3ac8
Nodo-> Dato: 27 Direcion: 0x3d3ac8 Siguiente: 0x3d3ad8
Nodo-> Dato: 68 Direcion: 0x3d3ad8 Siguiente: 0x3d3ae8
Nodo-> Dato: 62 Direcion: 0x3d3ae8 Siguiente: 0

Obviamente, internamente no existen las palabras nodo, dato,dirección y siguiente, es solo una representación.

Como una lista es una estructura de datos dinámica, el tamaño de la misma puede cambiar durante la ejecución del programa.

Como vimos en post anteriores, se puede generar memoria dinámicamente para un array, pero un array es una estructura estática pues su tamaño tiene un limite y así creáramos array dinámicos hay que redimensionar el tamaño si es necesario, lo cual ya implica un costo de volver a generar memoria dinámica.

Entonces podemos ver una ventaja de la listas sobre los arrays: No tener que redimensionar la estructura y poder agregar elemento tras elemento indefinidamente.

Cuando uno ya ha trabajado con arrays (vectores y matrices) y empieza a estudiar las listas, se da cuenta que una restricción de las listas es el acceso a los elementos. En un vector podíamos hacer algo como v[50] y nos estábamos refiriendo al índice 50 del vector v. A esto se le conoce como acceso aleatorio.

En el caso de las listas el acceso es secuencial, es decir, para acceder a un elemento del conjunto debemos de recorrer uno por uno los elementos hasta llegar al solicitado. Rápidamente se puede concluir que el tiempo de acceso a los elementos de un array es muchísimo más rápido que en una lista. Esta es una gran desventaja de las listas, por lo que buscar elementos por índice sería muy costoso. Esto no quiere decir que trabajar con arrays sea mejor que con listas. Las listas son muy flexibles y para muchos casos son imprescindibles.

Bueno, aquí va la primera práctica que hice sobre listas enlazadas. Implementación de una clase Lista, clase Nodo y los siguientes métodos:

  • Añadir un elemento al inicio.
  • Añadir un elemento al final
  • Añadir un elemento de manera ordenada
  • Llenar la lista por teclado
  • Llenar la lista aleatoriamente
  • Imprimir la lista
  • Buscar un elemento
  • Eliminar un elemento por dato
  • Eliminar un elemento por posicion o índice
  • Eliminar toda la lista
  • Invertir una lista
  • Ordernar una lista
  • Cargar una lista desde archivo
  • Guardar la lista en un archivo
  • Concatenar una lista a otra
  • Intersección entre 2 listas

Podrán ver la variable *temp en casi todos los métodos , este es el puntero de tipo Nodo que me va a permitir moverme a través de la lista y que inicialmente es igual a la cabeza (head). Mientras exista algo en la lista, voy avanzado el puntero para que apunte al siguiente. Esto se consigue en casi todos los casos con un while.

While(temp)
temp = temp->gte

Otra operación común en los métodos es preguntar si inicialmente la lista está vacía, es decir, si la cabeza no contiene algo o es igual a Null.

if(!head) o if(head==NULL)

Apliqué mis limitados conocimientos de templates para tener una lista genérica y así pueda funcionar con varios tipos de datos y de verdad funciona.

Ahí la definición e implementación de la clase, lista, clase nodo y el main para ver el funcionamiento. Cualquier crítica, sugerencia o comentarios son bienvenidos siempre.

Nodo.h

#ifndef NODO_H
#define NODO_H
#include <iostream>

using namespace std;
template <class T>
class Nodo
{
  public:
    T dato;
    Nodo *sgte;
    Nodo();
    Nodo(T);
    ~Nodo();

    void print();
    void eliminar();
};

#endif // NODO_H

Nodo.cpp

#include "Nodo.h"

//constructor por defecto
template<typename T>
Nodo<T>::Nodo()
{
  dato = NULL;
  sgte = NULL;
}

//constructor por parametro
template<typename T>
Nodo<T>::Nodo(T dato_)
{
  dato = dato_;
  sgte = NULL;
}

//imprimir un nodo
template<typename T>
void Nodo<T>::print()
{
  //cout << "Nodo-> " << "Dato: " << dato << " Direcion: " << this << " Siguiente: " << sgte << endl;
  cout << dato << "-> ";
}

//eliminar todos los nodos
template<typename T>
void Nodo<T>::eliminar()
{
  if(sgte)
    sgte->eliminar();
  delete this;
}

template<typename T>
Nodo<T>::~Nodo(){}

Lista.h

#ifndef LISTA_H
#define LISTA_H

#include <iostream>
#include "time.h"
#include <fstream>
#include <string>
#include "Nodo.h"
#include "Nodo.cpp"

using namespace std;

template <class T>
class Lista
{
  private:
    int dim,num_nodos;
    T line;
    string archivo;

  public:
    T ele;
    Nodo<T> *head;
    Lista();
    ~Lista();

    void llenar_teclado(int);
    void llenar_aleatorio(int);
    void print();
    void add_end(T);
    void add_head(T);
    void add_sort(T);
    void search(T);
    void del_dato(T);
    void del_pos(int);
    void del_all();

    void invertir();
    void sort();
    void load(string);
    void save(string);

    void concat(Lista);
    void interseccion(Lista);
};

#endif // LISTA_H

Lista.cpp

#include "Lista.h"

using namespace std;

//constructor por defecto
template<typename T>
Lista<T>::Lista()
{
  num_nodos = 0;
  head = NULL;
}

//llenar la lista por teclado
template<typename T>
void Lista<T>::llenar_teclado(int dim)
{
  for(int i=0;i<dim;i++){
    cout << "Ingresa el elemento " << i + 1 << endl;
    cin >> ele;
    add_end(ele);
  }
}

//llenar la lista aleatoriamente para enteros
template<typename T>
void Lista<T>::llenar_aleatorio(int dim)
{
  srand(time(NULL));
  for(int i=0;i<dim;i++){
    add_end(rand() % 100);
  }
}

//imprimir la lista
template<typename T>
void Lista<T>::print()
{
  Nodo<T> *temp = head;
  if(!head){
    cout << "La lista esta vacia " << endl;
  } else{
    while(temp){
      temp->print();
      if(!temp->sgte) cout << "NULL";
      temp = temp->sgte;
    }
  }
  cout << endl << endl;
}

//insertar al final
template<typename T>
void Lista<T>::add_end(T dato_)
{
  Nodo<T> *temp = head;
  Nodo<T> *nuevo = new Nodo<T> (dato_);

  if(!head){
      head = nuevo;
  } else{
    while(temp->sgte !=NULL){
      temp = temp->sgte;
    }
    temp->sgte = nuevo;
  }
  num_nodos++;
}

//insertar al inicio
template<typename T>
void Lista<T>::add_head(T dato_)
{
  Nodo<T> *temp = head;
  Nodo<T> *nuevo = new Nodo<T> (dato_);

  if(!head){
    head = nuevo;
  } else{
    nuevo->sgte = head;
    head = nuevo;
    while(temp){
      temp = temp->sgte;
    }
  }
  num_nodos++;
}

//insertar de manera ordenada
template<typename T>
void Lista<T>::add_sort(T dato_)
{
  Nodo<T> *nuevo = new Nodo<T> (dato_);
  Nodo<T> *temp = head;

  if(!head){
    head = nuevo;
  } else{
    if(head->dato > dato_){
      nuevo->sgte = head;
      head = nuevo;
    } else{
      while((temp->sgte != NULL) && (temp->sgte->dato < dato_)){
        temp = temp->sgte;
      }
      nuevo->sgte = temp->sgte;
      temp->sgte = nuevo;
    }
  }
  num_nodos++;
}

//buscar el dato de un nodo
template<typename T>
void Lista<T>::search(T dato_)
{
  Nodo<T> *temp = head;
  int cont = 1;
  int cont2=0;
  while(temp){
    if(temp->dato == dato_){
	  cout << "El dato se encuentra en la posicion: " << cont << " (Para seres humanos)" << endl;
	  cont2++;
    }
	temp = temp->sgte;
	cont++;
  }
  if(cont2 == 0){
    cout << "No existe el dato " << endl;
  }
  cout << endl << endl;
}

//eliminar por dato del nodo
template<typename T>
void Lista<T>::del_dato(T dato_)
{
  Nodo<T> *temp = head;
  Nodo<T> *temp1 = head->sgte;

  int cont=0;

  if(head->dato == dato_){
    head = temp->sgte;
  } else {
    while(temp1){
      if(temp1->dato == dato_){
	    Nodo<T> *aux = temp1;
        temp->sgte = temp1->sgte;
        delete aux;
        cont++;
        num_nodos--;
      }
	  temp = temp->sgte;
	  temp1 = temp1->sgte;
    }
  }
  if(cont == 0){
    cout << "No existe el dato " << endl;
  }
}

//eliminar por posicion del nodo
template<typename T>
void Lista<T>::del_pos(int pos)
{
  Nodo<T> *temp = head;
  Nodo<T> *temp1 = temp->sgte;

  if(pos < 1 || pos > num_nodos){
    cout << "Fuera de rango " << endl;
  } else if(pos==1){
    head = temp->sgte;
  } else{
    for(int i=2;i<=pos;i++){
      if(i==pos){
        Nodo<T> *aux = temp1;
        temp->sgte = temp1->sgte;
        delete aux;
        num_nodos--;
      }
      temp = temp->sgte;
      temp1 = temp1->sgte;
    }
  }
}

//eliminar todos los nodos
template<typename T>
void Lista<T>::del_all()
{
  head->eliminar();
  head = 0;
}

//invertir la lista
template<typename T>
void Lista<T>::invertir()
{
  Nodo<T> *temp = head;
  Nodo<T> *prev = NULL;
  Nodo<T> *next = NULL;

  while(temp){
    next = temp->sgte;
    temp->sgte = prev;
    prev = temp;
    temp = next;
  }
  head = prev;
}

//ordenar de manera ascendente
template<typename T>
void Lista<T>::sort()
{
  T aux2;
  Nodo<T> *aux = head;
  Nodo<T> *temp = aux;

  while(aux){
    temp=aux;
    while(temp->sgte){
      temp=temp->sgte;

      if(aux->dato>temp->dato){
        aux2=aux->dato;
        aux->dato=temp->dato;
        temp->dato=aux2;
      }
    }
    aux=aux->sgte;
  }
}

//cargar una lista de un archivo
template<typename T>
void Lista<T>::load(string archivo)
{
  ifstream in;
  in.open(archivo.c_str());

  if(!in.is_open()){
    cout << "No se puede abrir el archivo: " << archivo << endl << endl;
  } else{
    while(in >> line){
      add_end(line);
    }
  }
  in.close();
}

//guardar una lista en un archivo
template<typename T>
void Lista<T>::save(string archivo)
{
  Nodo<T> *temp = head;
  ofstream out;
  out.open(archivo.c_str());

  if(!out.is_open()){
    cout << "No se puede guardar el archivo " << endl;
  } else{
    while(temp){
      out << temp->dato;
      out << " ";
      temp = temp->sgte;
    }
  }
  out.close();
}

//concatenar a otra lista
template<typename T>
void Lista<T>::concat(Lista Lista2)
{
  Nodo<T> *temp2 = Lista2.head;

  while(temp2){
    add_end(temp2->dato);
    temp2 = temp2->sgte;
  }
}

//numeros que coinciden en 2 listas
template<typename T>
void Lista<T>::interseccion(Lista Lista2)
{
  Nodo<T> *temp = head;
  Nodo<T> *temp2 = Lista2.head;//cabeza de la segunda lista
  Lista lista_interseccion;//creo otra lista

  int num_nodos2 = Lista2.num_nodos;//nodos de la segunda lista
  int num_inter=0;//numero de coincidencias

  //creo 2 vectores dinamicos
  T *v1 = new T[num_nodos];
  T *v2 = new T[num_nodos2];

  //lleno los vectores v1 y v2 con los datos de la lista original y segunda lista respectivamente
  int i=0;
  while(temp){
    v1[i] = temp->dato;
    temp = temp->sgte;
    i++;
  }

  int j=0;
  while(temp2){
    v2[j] = temp2->dato;
    temp2 = temp2->sgte;
    j++;
  }

  //ordeno los vectores
  insert_sort(v1,num_nodos);
  insert_sort(v2,num_nodos2);

  int v1_i= 0;//indice del 1er vector (v1)
  int v2_i= 0;//indice del 2do vector (v2)

  //mientras no haya terminado de recorrer ambas listas
  while (v1_i < num_nodos && v2_i < num_nodos2) {
    if(v1[v1_i] == v2[v2_i]){//cuando los datos de ambas sean iguales
      lista_interseccion.add_end(v1[v1_i]);//utilizo mi metodo add_end para llenar la lista de intersecciones
      v1_i++;
      v2_i++;
      num_inter++;
    } else if(v1[v1_i] < v2[v2_i]){
      v1_i++;
    } else{
      v2_i++;
    }
  }

  //Solo si hay alguna interseccion imprimo la nueva lista creada
  if(num_inter > 0) {
    cout << "Existen " << num_inter << " intersecciones " << endl;
    lista_interseccion.print();
  } else {
    cout << "No hay interseccion en ambas listas" << endl;
  }
}

//usado por el metodo interseccion
template<typename T>
void insert_sort(T a[],int tam)
{
  T temp;
  for(int i=0;i<tam;i++){
    for(int j=i-1;j >=0 && a[j+1] < a[j];j--){
      temp = a[j+1];
      a[j+1] = a[j];
      a[j] = temp;
    }
  }
}

template<typename T>
Lista<T>::~Lista(){}

main.cpp

#include <iostream>
#include "Lista.h"
#include "Lista.cpp" //por errores de linking de tipo "undefined reference to" (estudiando)

using namespace std;
int main()
{
  Lista<int> l1;
  Lista<int> l2;

  int dim,pos;
  string archivo;

  cout << "Ingresa la dimension de la lista " << endl;
  cin >>dim;

  l1.llenar_aleatorio(dim);//llenar_teclado para otros tipos

  cout << "Lista A al inicio " << endl;
  l1.print();

  cout << "Agrega un elemento por la cabeza" << endl;
  cin >>l1.ele;
  l1.add_head(l1.ele);
  l1.print();

  cout << "Lista invertida " << endl;
  l1.invertir();
  l1.print();

  cout << "Lista ordenada " << endl;
  l1.sort();
  l1.print();

  cout << "Agrega un elemento (Sera ordenado)" << endl;
  cin >>l1.ele;
  l1.add_sort(l1.ele);
  l1.print();

  cout << "Busca un elemento" << endl;
  cin >>l1.ele;
  l1.search(l1.ele);

  cout << "Elimina un elemento por dato" << endl;
  cin >>l1.ele;
  l1.del_dato(l1.ele);
  l1.print();

  cout << "Elimina un elemento por posicion" << endl;
  cin >>pos;
  l1.del_pos(pos);
  l1.print();

  cout << "Cargar una lista desde archivo - Ingresa el nombre" << endl;
  cin >> archivo;//debe estar en el mismo directorio que este programa
  l2.load(archivo);
  cout << "Lista B" << endl;
  l2.print();

  cout << "Guardar la lista en un archivo - Ingresa el nombre" << endl;
  cin >> archivo;//ingresa un nombre cualquiera *.*
  l2.save(archivo);

  cout << "Interseccion entre las listas A y B " << endl;
  l1.interseccion(l2);

  cout << "Listas A y B concatenadas " << endl;
  l1.concat(l2);
  l1.print();

  l1.del_all();
  l1.print();

  return 0;
}

Un ejemplo de la salida del programa:

Ingresa la dimension de la lista
10
Lista A al inicio
55-> 60-> 31-> 5-> 4-> 51-> 9-> 27-> 68-> 62-> NULL

Agrega un elemento por la cabeza
18
18-> 55-> 60-> 31-> 5-> 4-> 51-> 9-> 27-> 68-> 62-> NULL

Lista invertida
62-> 68-> 27-> 9-> 51-> 4-> 5-> 31-> 60-> 55-> 18-> NULL

Lista ordenada
4-> 5-> 9-> 18-> 27-> 31-> 51-> 55-> 60-> 62-> 68-> NULL

Agrega un elemento (Sera ordenado)
45
4-> 5-> 9-> 18-> 27-> 31-> 45-> 51-> 55-> 60-> 62-> 68-> NULL

Busca un elemento
51
El dato se encuentra en la posicion: 8 (Para seres humanos)

Elimina un elemento por dato
60
4-> 5-> 9-> 18-> 27-> 31-> 45-> 51-> 55-> 62-> 68-> NULL

Elimina un elemento por posicion:
11
4-> 5-> 9-> 18-> 27-> 31-> 45-> 51-> 55-> 62-> NULL

Cargar una lista desde archivo - Ingresa el nombre
prueba.txt // mi archivito prueba.txt tenia estos datos
Lista B
8-> 59-> 23-> 15-> 94-> 63-> 9-> 17-> 62-> 33-> NULL

Guardar la lista en un archivo - Ingresa el nombre
otralista.txt // abre tu archivo y mira el contenido
Interseccion entre las listas A y B
Existen 2 intersecciones
9-> 62-> NULL

Listas A y B concatenadas
4-> 5-> 9-> 18-> 27-> 31-> 45-> 51-> 55-> 62-> 8-> 59-> 23-> 15-> 94-> 63-> 9->17-> 62-> 33-> NULL

La lista esta vacia.

Hasta pronto


Actions

Information

8 responses

8 07 2009
jandi manuel

te queria pedir ayuda haber si puedes hacer algo por mi tengo que entregar un proyecto y necesito leer lineas con datos de un archivo y almacenarlas en una lista dinamica..
no se como hacerlo..
ojala puedas ayudarme.saludos..gracias

5 08 2009
Javier

Un preguntilla, el puntero **matr1 no debería estar declarado como private para mantener la encapsulación?

Por lo demás, es un blog magnífico, estoy aprendiendo mucho.

saludos!!

2 09 2009
Ronny Yabar Aizcorbe

@jandi en este ejemplo te fijaste en los métodos
void Lista::load(string archivo)
void Lista::save(string archivo)

creo que eso es lo que necesitas, sino puedes especificar un poco más el problema que necesitas resolver.

16 09 2009
Ronny Yabar Aizcorbe

@Javier muchas gracias por el apunte, tienes toda la razón. **matr1 ahora es private.

3 09 2009
natalia

que quiere decir esto:
if(!head){ // esoo ?? EN C.
head = nuevo;

y …
Nodo *nuevo = new Nodo (dato_); // como podria escribirlo en C.
// (dato_); esooooo no entiendo

porfavor!!!
gracias =)

3 09 2009
natalia

haa ok , pero esto quiere decir ..
if(!head) // preguntar si cabeza es distinto de que .. ??

16 09 2009
Ronny Yabar Aizcorbe

@natalia la sentencia: if(!head) es una manera de preguntar si la variable es nula o no contiene nada.

25 10 2009
Daniel

muchas muchas gracias en serio bacano su codigo y mucho mas que lo comparta :) estamos en contacto por twitter.

Leave a comment