Lista Circular

¿Qué representa este TAD?

Una lista circular enlaza el último nodo con el primero, formando un anillo. Para recorrerla hay que reconocer cuándo se vuelve al inicio, en lugar de esperar un enlace nulo.

Prueba este ejemplo

En una Lista circular vacía carga 10, 20, 30. El último enlace debe volver a 10. Ejecuta Eliminar inicio: se retira 10 y el anillo queda formado por 20 y 30.

Qué observar · tres pasos

  1. Localiza el inicio y el enlace que cierra el anillo.
  2. Avanza paso a paso para ver las escrituras de enlaces, que pueden mostrar un estado transitorio.
  3. Comprueba que el anillo vuelve a cerrar al terminar; no esperes que todas las escrituras intermedias formen ya una lista completa.

Ir a la visualización e ingresar estos datos

De la idea al resultado

  1. Prepara una operación y predice qué cambiará.
  2. Activa Paso a paso antes de ejecutar si quieres ver la traza; sin ese modo obtienes el resultado final.
  3. Relaciona el dibujo con la línea C resaltada: acaba de ejecutarse. Una condición puede no cambiar ningún nodo.
  4. Lee las pistas de Comprender y las pestañas de Resultados.

Paso anterior / Siguiente paso revisan la misma llamada. Pausar no deshace; cerrar Paso a paso muestra su final sin ejecutarla otra vez. Restablecer TAD (o Reiniciar, según el módulo) borra estructura e historial, no sólo el cursor.

Usa −, +, 100%, Encajar vista y Centrar inicio para acomodar el gráfico. El zoom y el desplazamiento se conservan durante los pasos; la memoria auxiliar es opcional.

Agregar varios valores en esta estructura

Disponible en Pila, Cola, Lista enlazada, Lista circular, ABB, AVL, Rojo-Negro y Montículo binario. En las listas agrega al final. No está disponible para Grafos, Hash, Cola de prioridad ni Sublista; Ordenamiento conserva su propia entrada de arreglo.

  1. Abre Agregar varios en Preparar. Escribe hasta 50 enteros separados por comas, por ejemplo 8, -3, 12. Se permiten espacios y signos; no decimales, posiciones vacías ni una coma final. Rango: −2147483648 a 2147483647.
  2. Revisa cantidad y orden. Se valida la lista completa antes de empezar. ABB, AVL y Rojo-Negro no aceptan repetidos, ni dentro del lote ni respecto al árbol actual. Pila, Cola, listas y Montículo sí los admiten.
  3. Elige Cargar valores para añadirlos en orden con resultado final, o Agregar paso a paso para estudiar cada inserción.
  4. En paso a paso, usa los controles del lote. Reproducir traza sólo reproduce el valor actual; Pausar detiene la reproducción. Al final, pulsa Siguiente valor para aceptar la siguiente inserción, o Finalizar lote al terminar la última.

Al terminar o detener un lote, edita la lista antes de iniciar otro. La vista previa conserva el orden solicitado; el resumen describe ese lote, no sustituye el estado actual tras otra operación o Restablecer TAD.

Detener lote conserva las operaciones ya aceptadas, incluida la actual, y descarta los valores pendientes. No es «deshacer todo». Si falla una operación durante el proceso, las anteriores no se revierten. Si la respuesta es incierta, usa Recargar estado e historial y comprueba qué se guardó antes de volver a cargar valores.

Palabras básicas para leer el dibujo
TAD
Tipo abstracto de dato: los datos y las operaciones permitidas sobre ellos.
Nodo
Elemento que guarda un valor y enlaces a otros elementos.
Puntero o enlace
Referencia que permite llegar a otro nodo. NULL indica que no apunta a un nodo.
Raíz, padre e hijo
La raíz inicia el árbol; un padre se conecta con sus hijos.
Tope, frente y atrás
Tope (TOP) en Pila; frente (FRONT) y atrás (BACK) en Cola son referencias a sus extremos.

Primera práctica de 5 minutos · Copiar, ampliar y exportar · Resolver dudas y errores · Atajos y referencia

Profundizar: objetivo, reglas y memoria

Objetivo

Conservar el cierre y terminar recorridos seguros.

Estrategia

Seguir HEAD/TAIL y detectar la vuelta al inicio.

Invariante

cola->sgte == cabeza al terminar la operación, si quedan nodos; durante las escrituras se observa el estado intermedio.

Memoria dinámica

Actualizar el cierre antes de liberar el nodo retirado.

Errores frecuentes

Glosario contextual
Nodo
Objeto dinámico con datos y uno o más enlaces.
Enlace
Campo puntero que conecta objetos.
Alias
Dos punteros que designan el mismo objeto.
LIFO
El último en entrar es el primero en salir.
FIFO
El primero en entrar es el primero en salir.
Prioridad
Criterio de selección independiente del orden físico.
Circularidad
El último enlace vuelve al inicio.
malloc
Reserva memoria; puede devolver NULL.
free
Libera una reserva que deja de ser válida.
Referencia: consultas y resultados C
  • Buscar posiciones usa índices desde 1 y no modifica nodos. El C retorna el número total de coincidencias; copia solo las posiciones que caben en destino. NULL y capacidad 0 permiten contar sin copiar.
  • El main conserva búsquedas exitosas al recargar. En Circular reserva un arreglo de posiciones según las inserciones del historial: ejecuta la búsqueda e imprime las coincidencias calculadas por C.
  • Eliminar inicio devuelve el dato retirado en la aplicación; el método C devuelve true/false. Un nodo desconectado sigue reservado hasta free; el alias queda indeterminado después de liberarlo.
  • Invertir conserva reservas y cantidad; cambia enlaces y extremos. Limpiar libera todas las reservas de un anillo válido.

Alcance de esta pantalla

Referencia de operaciones y datos de entrada

Operaciones de la aplicación y datos

Buscar posiciones buscar_posiciones
Valor (entero)
Eliminar inicio eliminar_inicio
Sin datos adicionales.
Eliminar primero por valor eliminar_primero
Valor (entero)
Insertar final insertar_final
Valor (entero)
Insertar inicio insertar_inicio
Valor (entero)
Invertir invertir
Sin datos adicionales.
Limpiar limpiar
Sin datos adicionales.

Los métodos C siguientes pueden incluir helpers, utilidades sin botón propio y llamadores equivalentes. Un bloque con varias funciones no cambia el nombre de la operación de la aplicación. Las vistas de memoria usan identidades simbólicas; tras free, sus registros históricos no son punteros utilizables.

Ejecutar operación crea una llamada; Anterior/Siguiente recorren la misma traza. Cerrar y reactivar Paso a paso no vuelven a ejecutar. Las consultas completadas pueden conservarse en el historial o journal aunque no muten la estructura; los rechazos no agregan llamadas exitosas. Restablecer TAD (o Reiniciar, según el módulo) borra la estructura y su historial.

Referencia avanzada por operación

Abre sólo el tema que estás estudiando. Estos detalles distinguen el contrato C, su representación visual y las funciones auxiliares de la aplicación.

Contratos C y resultados

Las operaciones con retorno booleano distinguen éxito y rechazo mediante true/false. En una lista circular no vacía se conserva cola->sgte == cabeza al terminar; los enlaces transitorios se estudian dentro de la operación.

Buscar posiciones devuelve el total de coincidencias aunque el arreglo de salida sea menor. NULL y capacidad 0 permiten contar sin escribir posiciones; copiar valores, en cambio, devuelve cuántos valores se copiaron dentro de la capacidad disponible.

Una reserva fallida no enlaza un nodo parcial ni cambia cantidad o extremos. Destruir libera los nodos propios y deja los extremos en NULL. No confundas un registro histórico de memoria con un nodo vivo.

Representación, enlaces y consultas
  • Lista enlazada circular donde el último nodo apunta nuevamente al primero.
  • La interpretación paso a paso muestra cada escritura; el cierre del ciclo se restablece al terminar cada alta/baja de nodos para evitar rupturas o ciclos inválidos.

Código fuente completo del TAD

Descarga el archivo C principal de Lista circular y su encabezado para estudiarlo o compilarlo en tu equipo.

Referencia: estructura del TAD en C
struct lcir_nodo {
    int valor; /**< Dato almacenado. */
    struct lcir_nodo *sgte; /**< Enlace al siguiente nodo. */
};

typedef struct lcir_nodo LCirNodo;

typedef struct {
    LCirNodo *cabeza; /**< Primer nodo del anillo; NULL si está vacío. */
    LCirNodo *cola; /**< Último nodo del anillo; NULL si está vacío. */
    int cantidad; /**< Número de elementos almacenados. */
} ListaCircular;

Código C y llamadores equivalentes

Insertar al inicio · código C

Inserta nodo al inicio: enlaza el nuevo nodo antes del actual primero y actualiza la cabecera.

/**
 * @brief Inserta un nuevo elemento al inicio de la lista circular.
 *
 * @param[in,out] lista Puntero a la lista.
 * @param[in]     valor Dato a insertar.
 *
 * @return true si se insertó; false si lista es NULL o malloc falla.
 * @post En éxito cantidad aumenta en uno y cola->sgte enlaza con cabeza.
 * @note Si falla la reserva, conserva cabeza, cola, cantidad y enlaces anteriores.
 *
 * @post Si la lista estaba vacía, el nodo apunta a sí mismo.
 * @note Complejidad temporal: O(1) ya que se mantiene el puntero a la cola.
 */
bool lcir_insertar_inicio(ListaCircular *lista, int valor) {
    LCirNodo *nuevo;

    if (lista == NULL) {
        return false;
    }

    nuevo = lcir_crear_nodo(valor);
    if (nuevo == NULL) {
        return false;
    }

    if (lista->cabeza == NULL) {
        nuevo->sgte = nuevo;
        lista->cabeza = nuevo;
        lista->cola = nuevo;
        lista->cantidad = 1;
        return true;
    }

    nuevo->sgte = lista->cabeza;
    lista->cola->sgte = nuevo;
    lista->cabeza = nuevo;
    lista->cantidad++;
    return true;
}

/**
 * @brief Crea un nuevo nodo para la lista circular.
 *
 * @param[in] valor Entero a almacenar en el nuevo nodo.
 *
 * @return Puntero al nuevo nodo, o NULL si la asignación de memoria falla.
 * @post En éxito valor contiene el dato y sgte es NULL; todavía no pertenece al anillo.
 * @note El llamador adquiere la propiedad de la reserva y debe publicarla o liberarla.
 *
 * @note Complejidad temporal: O(1).
 */
static LCirNodo *lcir_crear_nodo(int valor) {
    LCirNodo *nodo = (LCirNodo *)malloc(sizeof(LCirNodo));
    if (nodo == NULL) {
        return NULL;
    }
    nodo->valor = valor;
    nodo->sgte = NULL;
    return nodo;
}
Insertar al final · código C

Recorre hasta el último nodo y conecta el nuevo elemento al final de la secuencia.

/**
 * @brief Inserta un nuevo elemento al final de la lista circular.
 *
 * @param[in,out] lista Puntero a la lista.
 * @param[in]     valor Dato a insertar.
 *
 * @return true si se insertó; false si lista es NULL o malloc falla.
 * @post En éxito cantidad aumenta en uno y cola->sgte enlaza con cabeza.
 * @note Si falla la reserva, conserva cabeza, cola, cantidad y enlaces anteriores.
 *
 * @note Complejidad temporal: O(1) debido a la existencia del puntero a la cola.
 */
bool lcir_insertar_final(ListaCircular *lista, int valor) {
    LCirNodo *nuevo;

    if (lista == NULL) {
        return false;
    }

    nuevo = lcir_crear_nodo(valor);
    if (nuevo == NULL) {
        return false;
    }

    if (lista->cabeza == NULL) {
        nuevo->sgte = nuevo;
        lista->cabeza = nuevo;
        lista->cola = nuevo;
        lista->cantidad = 1;
        return true;
    }

    nuevo->sgte = lista->cabeza;
    lista->cola->sgte = nuevo;
    lista->cola = nuevo;
    lista->cantidad++;
    return true;
}

/**
 * @brief Crea un nuevo nodo para la lista circular.
 *
 * @param[in] valor Entero a almacenar en el nuevo nodo.
 *
 * @return Puntero al nuevo nodo, o NULL si la asignación de memoria falla.
 * @post En éxito valor contiene el dato y sgte es NULL; todavía no pertenece al anillo.
 * @note El llamador adquiere la propiedad de la reserva y debe publicarla o liberarla.
 *
 * @note Complejidad temporal: O(1).
 */
static LCirNodo *lcir_crear_nodo(int valor) {
    LCirNodo *nodo = (LCirNodo *)malloc(sizeof(LCirNodo));
    if (nodo == NULL) {
        return NULL;
    }
    nodo->valor = valor;
    nodo->sgte = NULL;
    return nodo;
}
Eliminar inicio · código C

Remueve la cabecera, mueve el inicio al siguiente nodo y libera el nodo anterior.

/**
 * @brief Elimina el nodo de la cabeza de la lista circular.
 *
 * @param[in,out] lista Puntero a la lista circular.
 * @return true si se eliminó la cabeza; false si la lista era nula o vacía.
 * @post Si quedan nodos, la cola vuelve a enlazar con la nueva cabeza.
 * @post En éxito libera exactamente la reserva de la cabeza anterior.
 * @post Si era el único nodo, cabeza y cola quedan NULL y cantidad en 0.
 * @note Con varios nodos, cola->sgte apunta a la nueva cabeza al terminar.
 * @note La cantidad cambia en su escritura C; retirar alcance no equivale a free.
 */
bool lcir_eliminar_inicio(ListaCircular *lista) {
    LCirNodo *actual;

    if (lista == NULL || lista->cabeza == NULL) {
        return false;
    }

    actual = lista->cabeza;
    if (lista->cabeza == lista->cola) {
        lista->cabeza = NULL;
        lista->cola = NULL;
        lista->cantidad = 0;
        free(actual);
        return true;
    }

    lista->cabeza = actual->sgte;
    lista->cola->sgte = lista->cabeza;
    if (lista->cantidad > 0) {
        lista->cantidad--;
    }
    free(actual);
    return true;
}
Eliminar primera coincidencia · código C

Elimina la primera coincidencia de un valor y recompone enlaces para mantener continuidad.

/**
 * @brief Elimina el primer nodo que contenga el valor especificado.
 *
 * @param[in,out] lista Puntero a la lista.
 * @param[in]     valor Elemento a remover de la lista.
 *
 * @return true si el nodo fue eliminado, false si no se encontró.
 *
 * @post Reajusta cabeza y/o cola si fueron eliminadas.
 * @note Complejidad temporal: O(N) en el peor caso.
 * @note Lista NULL, vacía o valor ausente retorna false sin liberar nodos.
 * @post En éxito libera una sola reserva, la primera coincidencia desde cabeza.
 */
bool lcir_eliminar_primero(ListaCircular *lista, int valor) {
    LCirNodo *actual;
    LCirNodo *anterior;

    if (lista == NULL || lista->cabeza == NULL) {
        return false;
    }

    actual = lista->cabeza;
    anterior = lista->cola;
    do {
        if (actual->valor == valor) {
            if (actual == lista->cabeza && actual == lista->cola) {
                lista->cabeza = NULL;
                lista->cola = NULL;
                lista->cantidad = 0;
                free(actual);
                return true;
            }

            anterior->sgte = actual->sgte;
            if (actual == lista->cabeza) {
                lista->cabeza = actual->sgte;
            }
            if (actual == lista->cola) {
                lista->cola = anterior;
            }
            if (lista->cantidad > 0) {
                lista->cantidad--;
            }
            free(actual);
            return true;
        }
        anterior = actual;
        actual = actual->sgte;
    } while (actual != lista->cabeza);

    return false;
}
Buscar posiciones · código C

Recorre toda la lista y registra todas las posiciones donde aparece el valor buscado.

/**
 * @brief Busca un valor y guarda las posiciones (índices basados en 1) en las que aparece.
 *
 * @param[in]  lista     Puntero constante a la lista.
 * @param[in]  valor     Elemento a buscar.
 * @param[out] destino   Arreglo donde se escribirán las posiciones (1-based).
 * @param[in]  capacidad Capacidad máxima del arreglo de posiciones.
 *
 * @return El número de apariciones de dicho valor en la lista.
 *
 * @note Complejidad temporal: O(N), recorre la lista completa.
 * @note Retorna todas las coincidencias aunque destino sea NULL o capacidad sea insuficiente.
 * @note Solo copia el prefijo que cabe; con capacidad <= 0 no escribe posiciones.
 * @post No modifica nodos ni enlaces; lista NULL o sin cabeza retorna 0.
 */
int lcir_buscar_posiciones(const ListaCircular *lista, int valor, int *destino, int capacidad) {
    LCirNodo *actual;
    int encontrados = 0;
    int pos = 1;

    if (lista == NULL || lista->cabeza == NULL) {
        return 0;
    }

    actual = lista->cabeza;
    do {
        if (actual->valor == valor) {
            if (destino != NULL && encontrados < capacidad) {
                destino[encontrados] = pos;
            }
            encontrados++;
        }
        actual = actual->sgte;
        pos++;
    } while (actual != lista->cabeza);

    return encontrados;
}
Invertir · código C

Reasigna enlaces nodo a nodo para invertir la dirección completa de la lista.

/**
 * @brief Invierte la dirección de la lista circular internamente.
 *
 * @details
 * Recorre todos los nodos invirtiendo los punteros "sgte".
 * Al terminar, actualiza los punteros globales de cabeza y cola.
 *
 * @param[in,out] lista Puntero a la lista.
 *
 * @note Complejidad temporal: O(N).
 * @note Con lista NULL, vacía o un solo nodo no modifica la estructura.
 * @post Conserva cantidad y las mismas reservas; no usa malloc ni free.
 * @note Los enlaces intermedios pueden no formar todavía el anillo final.
 */
void lcir_invertir(ListaCircular *lista) {
    LCirNodo *prev;
    LCirNodo *curr;
    LCirNodo *next;
    LCirNodo *old_head;

    if (lista == NULL || lista->cabeza == NULL || lista->cabeza == lista->cola) {
        return;
    }

    prev = lista->cola;
    curr = lista->cabeza;
    do {
        next = curr->sgte;
        curr->sgte = prev;
        prev = curr;
        curr = next;
    } while (curr != lista->cabeza);

    old_head = lista->cabeza;
    lista->cabeza = lista->cola;
    lista->cola = old_head;
}
Limpiar · código C

Recorre la estructura liberando todos los nodos y deja el puntero raíz en estado nulo para reinicio seguro.

/**
 * @brief Libera la memoria de todos los nodos de la lista y la limpia.
 *
 * @param[in,out] lista Puntero a la lista.
 *
 * @post La estructura se reinicializa al estado vacío.
 * @note Complejidad temporal: O(N).
 * @note Con lista NULL o cabeza NULL retorna sin acceder ni normalizar otros campos.
 * @post Con un anillo válido libera cada reserva una vez y deja los dos extremos NULL y cantidad 0.
 * @note Los aliases externos de nodos liberados dejan de ser válidos.
 */
void lcir_destruir(ListaCircular *lista) {
    LCirNodo *actual;
    LCirNodo *next;

    if (lista == NULL || lista->cabeza == NULL) {
        return;
    }

    actual = lista->cabeza;
    if (lista->cabeza == lista->cola) {
        lista->cabeza = NULL;
        lista->cola = NULL;
        lista->cantidad = 0;
        free(actual);
        return;
    }

    while (actual != lista->cola) {
        next = actual->sgte;
        lista->cabeza = next;
        lista->cola->sgte = lista->cabeza;
        if (lista->cantidad > 0) {
            lista->cantidad--;
        }
        free(actual);
        actual = next;
    }

    lista->cabeza = NULL;
    lista->cola = NULL;
    lista->cantidad = 0;
    free(actual);
}

Ir a la visualización