Listas Circulares en Python: Creación y Uso Práctico
Resumen
¿Qué es una lista circularmente enlazada?
Las listas circularmente enlazadas son una variante intrigante de las listas enlazadas, donde el último nodo de la lista apunta de nuevo al primer nodo, creando así una estructura circular. Esta característica añade una versatilidad extra que no está presente en las listas lineales tradicionales, donde el último nodo apunta a null o a ninguno. Esta simple modificación permite realizar un recorrido circular a través de los nodos, lo cual puede ser útil en escenarios como el procesamiento continuo de datos o la implementación de estructuras de datos destinados a ciclos repetitivos.
¿Cómo implementar listas circularmente enlazadas en Python?
Al trabajar en Python, podemos crear una lista circularmente enlazada utilizando nuestra clase de nodo existente. Académicamente y en la práctica, este concepto es bastante directo si ya estás familiarizado con listas enlazadas. Aquí te mostramos cómo se puede lograr:
classNodo:def__init__(self, valor=None): self.valor = valor
self.next=None# Inicializar el primer nodohead = Nodo('jamón')head.next= head
# Pro es un auxiliar para recorrer la listapro = head
# Asumimos una estructura lista más extensawhileTrue: nuevo_nodo = Nodo('nuevo_valor')if pro.next== head:break pro = pro.nextpro.next= nuevo_nodo
nuevo_nodo.next= head
Como puedes observar, el último nodo nuevo_nodo apunta de nuevo al head, creando una estructura circular cohesionada.
¿Qué beneficios trae la estructura circular?
Trabajar con listas circularmente enlazadas ofrece varios beneficios:
Recorrido continuo: La estructura circular permite un recorrido ininterrumpido, útil en la programación de algoritmos que deben retornar al inicio tras alcanzar el final.
Memoria dinámica: Al no tener que encontrar el final de la lista, el manejo de la memoria es más flexible, permitiendo insertions y deletions más eficientes.
Estructura simple: Comparado con las listas doblemente enlazadas, las listas circularmente enlazadas son más simples en términos de definición de nodos y enlaces.
Sin duda, las listas circularmente enlazadas son una herramienta poderosa al tratar con estructuras de datos dinámicas.
Experimenta y comprueba
El reto que se presenta con las listas circularmente enlazadas es implementar y expandir tus propias listas con múltiples nodos, asegurándote de que el último nodo apunte al primero. Tal práctica es invaluable para fortalecer tus habilidades de programación y tu comprensión de las estructuras de datos. ¡Adelante, experimenta, comprueba tus resultados y profundiza en los detalles de las estructuras de datos circulares!
Próximamente, exploraremos otro tipo de estructura afín, las Double Linked Lists, que ofrecen otras funcionalidades interesantes y útiles. ¿Interesante, verdad? ¡Sigue explorando y ampliando tus capacidades!
Las listas enlazadas circulares son un tipo de lista enlazada en la que el último nodo apunta hacia el headde la lista en lugar de apuntar None. Esto es lo que los hace circulares.
Las listas enlazadas circulares tienen bastantes casos de uso interesantes:
Dar la vuelta al turno de cada jugador en un juego multijugador
Gestionar el ciclo de vida de la aplicación de un sistema operativo determinado
Implementando un montón de Fibonacci
Así es como se ve una lista enlazada circular:
Una de las ventajas de las listas enlazadas circulares es que puede recorrer toda la lista comenzando en cualquier nodo. Dado que el último nodo apunta al headde la lista, debe asegurarse de dejar de atravesar cuando llegue al punto de partida. De lo contrario, terminarás en un bucle infinito.
En términos de implementación, las listas enlazadas circulares son muy similares a la lista enlazada individualmente. La única diferencia es que puede definir el punto de partida cuando recorre la lista:
classCircularLinkedList: def __init__(self): self.head=None def traverse(self, starting_point=None):if starting_point is None: starting_point = self.head node = starting_point
while node is not Noneand(node.next!= starting_point):yield node
node = node.nextyield node
def print_list(self, starting_point=None): nodes =[]for node in self.traverse(starting_point): nodes.append(str(node))print(" -> ".join(nodes))
Atravesar la lista ahora recibe un argumento adicional starting_point, que se usa para definir el inicio y (debido a que la lista es circular) el final del proceso de iteración. Aparte de eso, gran parte del código es el mismo que teníamos en nuestra LinkedListclase.
from typing import Optional
from typing import Any
from node import Node
classCicularLinkedList: _head: Optional[Node]=None _size:int=0def__init__(self,*items):"""Initializes the circular linked list.""" self._head =None self._size =0for item in items: self.append(item)@propertydefsize(self)->int:"""Returns the size of the circular linked list."""return self._size
defappend(self, item: Any)->None:"""Appends an item to the end of the list.
Time complexity: O(n)
"""if self._head isNone: self._head = Node(item) self._head.next= self._head
else: current = self._head
while current.next!= self._head: current = current.next current.next= Node(item) current.next.next= self._head
self._size +=1defiter(self):"""Iterates through the circular linked list.""" current = self._head
while current.next!= self._head: value = current.value
current = current.nextyield value
yield current
defiternodes(self):"""Iterates through the circular linked list.""" current = self._head
while current.next!= self._head:yield current
current = current.nextyield current
def__iter__(self)->None:return self.iter()def__len__(self)->int:return self._size
def__str__(self)->str: list_items =list(self.iter())return' -> '.join(str(item)for item in list_items)if __name__ =='__main__':import unittest
classCircularLinkedListTestCase(unittest.TestCase):deftest_initialize_empty(self): self.assertEqual(len(CicularLinkedList()),0)deftest_initialize_with_items(self): self.assertEqual(len(CicularLinkedList(1,2,3)),3)deftest_append(self): cll = CicularLinkedList() cll.append(1) cll.append(2) cll.append(3) self.assertEqual(len(cll),3)deftest_iter(self): cll = CicularLinkedList(1,2,3) self.assertEqual(list(cll),[1,2,3])deftest_first_node_linked_with_last_node(self): cll = CicularLinkedList(1,2,3) head = cll._head
for item in cll.iternodes():pass self.assertIs(head, item.next) unittest.main(verbosity=2)
Mi solución para crear una lista circular enlazada de verios nodos.
from node importNode# Generación de lista de nodos enlazados
head =Nonefor count inrange(1,10): head =Node(count, head)# Referenciar el ultimo nodo(tail) con el primer nodo(head)probe = head
while probe.next!=None: probe = probe.nextprobe.next= head
# Agregar un nuevo elemento por "indice"(head a tail)index =3new_item ='ham'probe = head
if index <=0: head =Node(new_item, head)else: probe = head
while index >1 and probe.next!= head: probe = probe.next index -=1 probe.next=Node(new_item, probe.next)# # Recorrer lista de manera circular
steps =15probe = head
while steps >0:print(probe.data, end=' ') probe = probe.next steps -=1print()# Recorrer lista una vez
probe = head
while probe.next!= head:print(probe.data, end=' ') probe = probe.nextprint(probe.data)
Es un poco malo tener que decir que la clase no tiene un verdadero valor de conocimiento, pero enserio el profe solo se pone a escribir el código y decir lo que literal se entiende sin que lo explique. Además, este tema no es complicado pero el profe lo hace ver aburrido y eso es malo, porque hace que mejor uno vaya a Youtube o a un artículo donde se explica de mejor manera este tema. Platzi te pido que renueves el curso y por mi parte voy a dejar hasta aquí este curso porque en Youtube hay videos que lo explican mejor.
Despues de harto debugging Lo resolví de la siguiente manera
Para hacerlo tuve que corregir varias cosas en el linked_list, le agregé una self.head, y modifiqué el método append para que head sea siempre fuera el primero nodo y para que tail siempre fuera el último, error que hace dos clases causo estragos en los cometarios de este curso
También añadí un nuevo método llamado Show para visualizar los nodos
y se ve así
Cuándo se vuelve circular la iteración nunca termina, por ende limité a que pasara dos veces por el head, por eso en la 2da imagen tuve que crear el head
Hi Guys, i comparte my solution for circle linked list, in this solution you can insert the nodes number with your respective value
from node importNodeclassCircleLinkedList: def __init__(self)->None: self.head=None self.size=0 def append(self, data):"""Insert a newNodedata: is of value forNode"""
node =Node(data)if self.head==None: self.head= node
self.head.next= self.headelse: node.next= self.head current = self.headwhile current.next!= self.head: current = current.next current.next= node
self.size+=1 def print(self):"""Print all value of nodes
"""
current = self.head node_numbers = self.sizewhile node_numbers >0:print(f'The Node {current} have value {current.data}') current = current.next node_numbers -=1if __name__ =='__main__': node_numbers =int(input('Insert numbers of Nodes: ')) node =CircleLinkedList() total_nodes = node_numbers
while node_numbers >0: data_node =str(input(f'¿What is the value of node number {total_nodes - (node_numbers - 1)}?: ')) node.append(data_node) node_numbers -=1 node.print()
Las linked lists tienen un gran número de aplicaciones, pues a diferencia de una singly o doubly linked list permite iterar de forma circula (valga la redudancia) por todos sus elementos ayudando a reducir la complejidad algorítmica.
Un ejemplo puede ser nuestro sistema operativo, donde las aplicaciones son iteradas en forma concurrente para ejecutar sus procesos.
Un caso más sencillo puede ser un videojuego multiplayer tipo Mario Party donde cada jugador es un nodo. O incluso puedes crear una circular queue (queue basado en nodos) donde se atienda de forma concurrente front y rear.
# Definimos una clase para los nodos de la listaclassNode:def__init__(self, data, next_node): self.data = data # Dato que almacena el nodo self.next= next_node # Referencia al siguiente nodo# Creamos un nodo centinela (no contiene datos útiles, solo sirve como referencia inicial/final)head = Node(None,None)head.next= head # El centinela apunta a sí mismo, indicando que la lista está vacía (lista circular)# Función para insertar un nodo al final de la listadefinsert_at_end(head, data): new_node = Node(data, head)# Creamos el nuevo nodo apuntando al centinela (como siguiente nodo) probe = head # Comenzamos desde el nodo centinela# Recorremos la lista hasta encontrar el último nodo (el que apunta al centinela)while probe.next!= head: probe = probe.next probe.next= new_node # Enlazamos el último nodo con el nuevo nodo# Insertamos algunos elementos al final de la listainsert_at_end(head,"ham")insert_at_end(head,"cheese")insert_at_end(head,"lettuce")# Función para imprimir los elementos de la listadefprint_list(head): probe = head.next# Empezamos con el primer nodo real (después del centinela)while probe != head:# Recorremos hasta volver al centinelaprint(probe.data)# Imprimimos el dato del nodo actual probe = probe.next# Avanzamos al siguiente nodo# Mostramos el contenido de la listaprint_list(head)
Antes de la inserción:head/probe
| v
+-----++-----+|None|--->|None|+-----++-----+Después de la inserción:
head probe
|| v v
+-----++-----++-----+|None|--->|'ham'|--->|None|+-----++-----++-----+
Antes de la inserción:head/probe
| v
+-----++-----+|None|--->|None|+-----++-----+Después de la inserción:
head probe
|| v v
+-----++-----++-----+|None|--->|'ham'|--->|None|+-----++-----++-----+```Antes de la inserción:head/probe
 |  v
+-----++-----+|None|--->|None|+-----++-----+Después de la inserción:
head probe
 ||  v v
+-----++-----++-----+|None|--->|'ham'|--->|None|+-----++-----++-----+
Me encanta como volví un for loop que normalmente se usa para iterar un numero definido de veces en un ciclo infinito.
from data_structures.linked_listimportSinglyLinkedListwords =SinglyLinkedList()words.append("Hola")words.append("Esto")words.append("Es")words.append("Una")words.append("lista")words.append("Enlazada")words.append("Circular")head = words.headmovement = head
while movement.next!=None: movement = movement.nextmovement.next= head
contador =0print("Lista circular")for word in words.iter():print(word.data) contador +=1if contador ==30:print(f"Lista infinita en un for loop vamos en {contador} iteraciones")break