Qué son las listas circulares en C

Qué son las listas circulares en C

El objetivo de este artículo es que el lector pueda entender lo que son las listas circulares en el lenguaje de programación C.

¿Cuáles son los requisitos?

Los requisitos son los siguientes: los tipos de datos, las estructuras, el uso de typedef, los punteros, la función usuario, las listas enlazadas simples y las listas doblemente enlazadas.

¿Cuál es la definición de una lista circular?

La lista circular es una especie de lista enlazada simple o doblemente enlazada, pero que posee una característica adicional para el desplazamiento dentro de la lista: esta no tiene fin.

Para que la lista no tenga fin, el puntero siguiente del último elemento apuntará hacia el primer elemento de la lista en lugar de apuntar al valor NULL, como hemos visto en el caso de listas enlazadas simples o doblemente enlazadas.

En las listas circulares, nunca se llega a una posición en la que ya no sea posible desplazarse. Cuando se llegue al último elemento, el desplazamiento volverá a comenzar desde el primer elemento:

¿Cómo construir el modelo de un elemento de la lista?

Para definir un elemento de la lista, el tipo struct será utilizado. El elemento de la lista va a tener un campo dato y un puntero siguiente. Este debe ser del mismo tipo que el elemento, en caso contrario no podrá apuntar hacia el elemento. El puntero siguiente permitirá el acceso al próximo elemento:

typedef struct ElementoLista {
    char *dato;
    struct ElementoLista *siguiente;
}Elemento;

Para tener el control de la lista es mejor guardar ciertos elementos: el primer elemento, el último elemento y el número de elementos. Para ello, otra estructura será utilizada (no es obligatorio, pueden ser utilizadas variables):

typedef struct ListaIdentificar {
    Elemento *inicio;
    Elemento *fin;
    int tamaño;
}Lista;

El puntero inicio albergará la dirección del primer elemento de la lista. El puntero fin contendrá la dirección del último elemento de la lista. La variable tamaño contiene el número de elementos.

Cualquiera que sea la posición en la lista, los punteros inicio y fin siempre apuntarán hacia el primer y el último elemento respectivamente. El campo tamaño contendrá el número de elementos de la lista cualquiera sea la operación efectuada sobre la lista.

¿Cuáles son las operaciones sobre las listas circulares?

Inicialización

Modelo de la función:

void inicialización (Lista *lista);

Esta operación debe ser realizada antes que cualquier otra operación sobre la lista. Comienza colocando el puntero inicio y fin con el puntero NULL, y el tamaño con el valor 0.

La función:

void inicialización (Lista *lista){
    lista->inicio = NULL;
    lista->fin = NULL;
    tamaño = 0;
}

Inserción de un elemento en la lista

A continuación, veamos el algoritmo de inserción y el registro de los elementos: declaración del elemento que se insertará, asignación de la memoria destinada al nuevo elemento, llenar el contenido del campo de los datos, actualizar los punteros hacia el primer y último elemento si fuese necesario.

Caso particular: en una lista con un solo elemento, el primer elemento es al mismo tiempo el último. Luego, se tiene que actualizar el tamaño de la lista.

Inserción en una lista vacía

Modelo de la función:

int ins_lista_circ_vacia(Lista * lista, char *dato);

La función devuelve -1 en caso de error, si no devuelve 0.

Etapas: asignación de memoria para el nuevo elemento, rellenar el campo de datos del elemento nuevo, el puntero siguiente del nuevo elemento apuntará hacia sí mismo (es la etapa que vuelve a la lista circular), los punteros inicio y fin apuntarán hacia el nuevo elemento y el tamaño es actualizado:

La función:

/* inserción en una lista vacía */
int ins_lista_circ_vacia(Lista * lista, char *dato){
    Elemento *nuevo_elemento;
    if ((nuevo_elemento = (Elemento *) malloc (sizeof (Elemento)))
          == NULL)
      return -1;       
    if ((nuevo_elemento->dato = (char *) malloc (50 * sizeof (char)))
            == NULL)
       return -1;
    strcpy (nuevo_elemento->dato, dato);

    nuevo_elemento->siguiente = nuevo_elemento;
    lista->inicio = nuevo_elemento;
    lista->fin = nuevo_elemento;
    lista->tamaño++;
    return 0;
}

Inserción en una lista no vacía

Modelo de la función:

int ins_lista_circ(Lista * lista, Elemento *actual, char *dato);

La función devuelve -1 en caso de equivocación, si no devuelve 0.

La inserción se efectuará al final de la lista.

Etapas: asignación de memoria para el nuevo elemento, rellenar el campo de datos del reciente elemento, el puntero siguiente del nuevo elemento apunta hacia la dirección del primer elemento (conservar la lista circular), el puntero inicio no cambia, el puntero fin apunta hacia el nuevo elemento, el tamaño se incrementa en una unidad:

La función:

/* inserción en una lista no vacía */
int ins_lista_circ(Lista * lista, Elemento *actual, char *dato){
    Elemento *nuevo_elemento;
    if ((nuevo_elemento = (Elemento *) malloc (sizeof (Elemento)))
            == NULL)
        return -1;       
    if ((nuevo_elemento->dato = (char *) malloc (50 * sizeof (char)))
            == NULL)
        return -1;
    strcpy (nuevo_elemento->dato, dato);

    if(actual != lista->fin)
        return -1;
  
    nuevo_elemento->siguiente = actual->siguiente;
    actual->siguiente = nuevo_elemento;
    lista->fin = nuevo_elemento;
    lista->tamaño++;
    return 0;
}

Eliminación de un elemento en la lista

Veamos el algoritmo de eliminación de un elemento de la lista: uso de un puntero temporal para guardar la dirección de los elementos que se van a eliminar, el elemento a eliminar se encontrará después del elemento actual.

El puntero siguiente del elemento actual apunta hacia la dirección del puntero siguiente del elemento que se eliminará, libera la memoria ocupada por el elemento eliminado y actualiza el tamaño de la lista.

Para eliminar un elemento de la lista hay varias situaciones: eliminación dentro de la lista y eliminación del último elemento de la lista.

Eliminación al inicio de la lista

Modelo de la función:

int sup_list_circ(Lista *lista);

La función devuelve -1 en caso de un error, si no devuelve 0.

Etapas: el puntero sup_elemento contendrá la dirección del elemento 1, el puntero inicio apuntará hacia el segundo elemento, el puntero siguiente del último elemento apuntará hacia el primer elemento (que era el segundo antes de la eliminación), el tamaño de la lista disminuirá 1 elemento:

La función:

/* eliminación al inicio de la lista */
int sup_lista_circ(Lista * lista){
    if (lista->tamaño < 2)
        return -1;
    Elemento *sup_element;

    sup_elemento = lista->inicio;
    lista->inicio = lista->inicio->siguiente;
    lista->fin->siguiente = lista->inicio;

    free (sup_elemento->dato);
    free (sup_elemento);
    lista->tamaño--;
    return 0;
}

Eliminación en una lista con un solo elemento

Modelo de la función:

int sup_list_circ_unica(Lista *lista);

La función devuelve -1 en caso de algún error, si no devuelve 0.

Etapas: el puntero sup_elemento contendrá la dirección del elemento (la lista contiene un solo elemento), el puntero inicio apuntará hacia NULL, el puntero fin apuntará hacia NULL y el tamaño de la lista disminuirá un elemento:

La función:

/* eliminación en una lista con un solo elemento*/
int sup_lista_circ_unica(Lista *lista){
    if (lista->tamaño != 1)
        return -1;
    Elemento *sup_elemento;

    sup_elemento = lista->inicio;
    lista->inicio = NULL;
    lista->fin = NULL;

    free (sup_elemento->dato);
    free (sup_elemento);
    lista->tamaño--;
    return 0;
}

Mostrar la lista

Para mostrar la lista completa, es necesario posicionarse al inicio de la lista (el puntero inicio lo permitirá). Después, utilizando el puntero siguiente de cada elemento, la lista es recorrida del primer al último elemento.

En comparación con las listas simples y doblemente enlazadas, en las que la condición para detenerse está dada por el puntero siguiente del último elemento, que vale NULL, para la lista circular no hay punto de detención a menos que elijamos uno.

A continuación, dos variantes de visualización: mostrar la lista (del primer al último elemento) y mostrar la lista sin una condición para detenerse.

Mostrar la lista (del primer al último elemento)

Utilizaremos el tamaño de la lista como la condición para detenerse.

La función:

/* mostrar la lista */
void mostrar (Lista * lista){
    Elemento *actual;
    actual = lista->inicio;
    int i;
    for(i=0;i<lista->tamaño;++i){
        printf ("%p - %s\n", actual, actual->dato);
        actual = actual->siguiente;
    }
}

Mostrar la lista sin una condición para detenerse (indefinidamente)

La función:

/* recorrer la lista indefinidamente*/
void mostrar_indefinidamente (Lista * lista){
    Elemento *actual;
    actual = lista->inicio;
    while (1){
        printf ("%p - %s\n", actual, actual->dato);
        actual = actual->siguiente;
    }
}

Destrucción de la lista

Para destruir la lista completa utilizamos la eliminación al inicio de la lista ya que el tamaño es mayor a 1, y luego la eliminación en una lista con un solo elemento.

La función:

/* destruir lista */
void destruir (Lista * lista){
    while (lista->tamaño > 0){
        if(lista->tamaño > 1)
        sup_lista_circ (lista);
    else
        sup_lista_circ_unica(lista);
    }
}

¿Cuál es un ejemplo completo?

lista_circ.h

/* ---------- lista_circ.h ----------- */
typedef struct ElementoListaCirc {
    char *dato;
    struct ElementoListaCirc *siguiente;
} Element;

typedef struct ListaIdentificarCirc {
    Elemento *inicio;
    Elemento *fin;
    int tamaño;
} Lista;

/* inicialización de la lista */
void inicialización (Lista * lista);

/* INSERCION */
/* inserción en una lista vacia */
int ins_lista_circ_vacia(Lista * lista, char *dato);
int ins_lista_circ(Lista * lista, Elemento *actual, char *dato);

/* ELIMINACION */
int sup_lista_circ (Lista * lista);
int sup_lista_circ_unica (Lista * lista);

int menu (Lista *lista);
void mostrar (Lista * lista);
void mostrar_infinito (Lista * lista);
void destruir (Lista * lista);
/* -------- FIN lista_circ.h --------- */

lista_circ_function.h

/* ---------- lista_circ.function.h ----------- */
void inicialización (Lista * lista){    
lista->inicio = NULL;    
lista->fin = NULL;    
lista->tamaño = 0;}

/* inserción en una lista vacia */
int ins_lista_circ_vacia(Lista * lista, char *dato){    
Elemento *nuevo_elemento;    
if ((nuevo_elemento = (Elemento *) malloc (sizeof (Elemento)))          == NULL)      return -1;           if ((nuevo_elemento->dato = (char *) malloc (50 * sizeof (char)))         == NULL)       return -1;    strcpy (nuevo_elemento->dato, dato);    
nuevo_elemento->siguiente = nuevo_elemento;   
lista->inicio = nuevo_elemento;    
lista->fin = nuevo_elemento;    
lista->tamaño<gras>;    
return 0;
}

/* inserción en una lista no vacia */
int ins_lista_circ(Lista * lista, Elemento *actual, char *dato){   
Elemento *nuevo_elemento;    
if ((nuevo_elemento = (Elemento *) malloc (sizeof (Elemento)))         == NULL)        return -1;           if ((nuevo_elemento->dato = (char *) malloc (50 * sizeof (char)))        == NULL)        return -1;    strcpy (nuevo_elemento->dato, dato);    
if(actual != lista->fin)        return -1;     
nuevo_elemento->siguiente = actual->siguiente;    
actual->siguiente = nuevo_elemento;    
lista->fin = nuevo_elemento;   
lista->tamaño<gras>;    
return 0;
}

/* eliminación al inicio de la lista */
int sup_lista_circ(Lista * lista){    
if (lista->tamaño < 2)        return -1;    
Elemento *sup_elemento;    
sup_elemento = lista->inicio;    
lista->inicio = lista->inicio->siguiente;    
lista->fin->siguiente = lista->inicio;    
free (sup_elemento->dato);    
free (sup_elemento);    
lista->tamaño--;    
return 0;
}

/* eliminación en una lista con un solo elemento*/
int sup_lista_circ_unica(Lista *lista){    
if (lista->tamaño != 1)        return -1;    
Elemento *sup_elemento;    
sup_elemento = lista->inicio;    
lista->inicio = NULL;    
lista->fin = NULL;    
free (sup_elemento->dato);    
free (sup_elemento);    
lista->tamaño--;    
return 0;
}

/* mostrar la lista */
void mostrar (Lista * lista){    
Elemento *actual;    
actual = lista->inicio;    
int i;    
for(i=0;i<lista->tamaño;++i){        
printf ("%p - %s\n", actual, actual->dato);        
actual = actual->siguiente;    
}
}

/* recorrer la lista indefinidamente*/
void mostrar_infinito (Lista * lista){    
Elemento *actual;    
actual = lista->inicio;    
while (1){        
printf ("%p - %s\n", actual, actual->dato);        
actual = actual->siguiente;    
}
}

/* destruir esta lista */
void destruir (Lista * lista){    
while (lista->tamaño > 0){        
if(lista->tamaño > 1)  sup_lista_circ (lista);    
else sup_lista_circ_unica(lista);    
}
}
int menu (Lista *lista){    
int elección;     
printf("********** MENU **********\n");    
if (lista->tamaño == 0){        
printf ("1. Adicion del 1er elemento\n");        
printf ("2. Quitar\n");    
}
else 
{               
printf ("1. Adición de un elemento\n");        
printf ("2. Eliminación al inicio (la lista debe tener al menos 2 elementos)\n");    
printf ("3. Eliminación en una lista con un solo elemento\n");        
printf ("4. Mostrar lista circular\n");        
printf ("5. Mostrar lista circular [Ctrl-C] para quitar el programa\n");        
printf ("6. Destruir la lista\n");        
printf ("7. Quitar\n");    
}    
printf ("\n\nElegir: ");    
scanf ("%d", &elección);    
getchar();    
if(lista->tamaño == 0 && elección == 2) elección = 7;    
return elección;
}

/* -------- FIN lista_circ_function --------- */

lista_circ.c

/* ---------- lista_circ.c ----------- */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "lista_circ.h"
#include "lista_circ_function.h"
int main (void) {    
char elección;    
char *nom;    
Lista *lista;    
Elemento *actual;    
if ((lista = (Lista *) malloc (sizeof (lista))) == NULL) return -1;    
if ((nombre = (char *) malloc (50)) == NULL) return -1;    
actual = NULL;    
elección = 'o';    
inicialización (lista);    
while (elección != 7) {        
elección = menu (lista);        
switch (elección) {            
case 1:                
printf ("Ingrese un elemento: ");                
scanf ("%s", nombre);                
getchar ();                
if(lista->tamaño == 0) ins_lista_circ_vacia (lista,nombre);                
else ins_lista_circ (lista,lista->fin,nombre);                
printf ("%d elements:deb=%s, fin=%s\n", lista->tamaño,                               lista->inicio->dato, lista->fin->dato);                
mostrar (lista);                
break;            
case 2:                
if(lista->tamaño < 2) break;                
sup_lista_circ (lista);                
if (lista->tamaño != 0) printf ("%d elements:deb=%s, fin=%s\n",                                            lista->tamaño, lista->inicio->dato, lista->fin->dato);                
mostrar(lista);                
break;            
case 3:                
if(lista->tamaño != 1) break;                
sup_lista_circ_unica(lista);                
printf("La lista está vacia\n");                
break;            
case 4:                
mostrar(lista);                
break;            
case 5:                
mostrar_infinito(lista);                
break;            
case 6:                
destruir (lista);                
printf ("la lista ha sido destruida: %de elementos\n", lista->tamaño);                
break;        
}    
}    
return 0;
}
Alrededor del mismo tema

Lenguajes