Antes de escribir la primera función de una lista enlazada en Python necesitamos manejar con soltura nociones clave del lenguaje: cómo se construye un nodo con clases, qué implica trabajar con referencias, cómo se gestiona la memoria con el recolector de basura y cuáles son los errores que pueden dejar la estructura inconsistente. Cada subsección profundiza estos pilares para que, al llegar a la implementación, nada resulte misterioso.
El nodo es la unidad mínima de una lista enlazada. Contiene datos y enlaces que definen su posición relativa. Visualmente se puede pensar como dos campos contiguos: a la izquierda el valor y a la derecha la referencia al siguiente nodo (denominado sig). Independientemente del tipo de lista (simple, doble o circular) todos comparten este concepto; en variantes dobles se agrega una referencia ant para retroceder.
class Nodo:
def __init__(self, valor):
self.valor = valor
self.sig = None # referencia al nodo siguiente
La definición anterior declara un Nodo con dos campos. Si necesitamos una lista doble, bastaría con agregar self.ant = None en el constructor. Como el head de la lista suele ser None cuando está vacía, los nodos se irán creando y enlazando a medida que los insertamos.
Enfoquémonos en un diseño orientado a objetos, pero sin separar en módulos. Un solo archivo Python puede definir la clase Nodo, la clase ListaEnlazada y un bloque principal de ejecución. Esto mantiene el ejemplo auto contenido y fácil de probar con python archivo.py.
# lista.py
class Nodo:
def __init__(self, valor):
self.valor = valor
self.sig = None
class ListaEnlazada:
def __init__(self):
self.cabeza = None
def insertar_inicio(self, valor):
nuevo = Nodo(valor)
nuevo.sig = self.cabeza
self.cabeza = nuevo
def esta_vacia(self):
return self.cabeza is None
La clase mantiene el estado interno y ofrece métodos que protegen los invariantes (por ejemplo, no hay forma de perder la cabeza sin que un método la actualice). Puedes seguir extendiendo la clase con operaciones de impresión y borrado, manteniendo todo en un solo archivo ejecutable.
Una lista enlazada en Python depende de referencias a objetos. Cada campo sig guarda una referencia al siguiente nodo, no una copia. Al reasignar sig conectamos o desconectamos nodos reales. Entender cómo se pasa y se reasigna una referencia es vital para evitar perder nodos.
lista = ListaEnlazada()
lista.insertar_inicio(42)
# las referencias se mueven dentro del método:
# nuevo.sig = self.cabeza
# self.cabeza = nuevo
El método insertar_inicio mueve las referencias en el orden correcto para que ningún nodo quede perdido. Siempre conviene inicializar la cabeza como None para detectar usos indebidos durante la depuración.
El crecimiento de la lista depende de crear nodos según sea necesario. Python gestiona la memoria automáticamente: cuando un objeto deja de tener referencias vivas, el recolector de basura lo libera. No hay que llamar a free, pero sí conviene romper enlaces para que los nodos queden libres al eliminar elementos.
class ListaEnlazada:
# ...
def eliminar_inicio(self):
if self.cabeza is None:
return None
eliminado = self.cabeza
self.cabeza = eliminado.sig
eliminado.sig = None # rompe el enlace para que el GC pueda liberar
return eliminado.valor
Encapsular la eliminación en métodos que rompan referencias evita retener nodos en ciclos y delega en el recolector. En estructuras enlazadas grandes, cortar referencias circulares acelera la liberación.
El trabajo con referencias también trae riesgos que conviene dominar antes de avanzar:
sig en el orden correcto: si se pisa la referencia a la cabeza antes de enlazar el nuevo nodo, se pierden elementos.sig al nodo equivocado puede formar bucles que impidan liberar nodos o provoquen recorridos infinitos.while sin guardar la referencia al siguiente rompe el recorrido.Para prevenir estos problemas es recomendable encapsular las operaciones de inserción y borrado en funciones probadas, y escribir pruebas pequeñas que verifiquen el estado de la lista tras cada operación.
Con los conceptos anteriores podemos crear un archivo único listo para ejecutar con python listas_enlazadas.py. El mismo script define las clases y contiene el bloque principal.
# listas_enlazadas.py
class Nodo:
def __init__(self, valor):
self.valor = valor
self.sig = None
class ListaEnlazada:
def __init__(self):
self.cabeza = None
def insertar_inicio(self, valor):
nuevo = Nodo(valor)
nuevo.sig = self.cabeza
self.cabeza = nuevo
def imprimir(self):
reco = self.cabeza
while reco:
print(reco.valor, end=" -> ")
reco = reco.sig
print("None")
def limpiar(self):
while self.cabeza:
siguiente = self.cabeza.sig
self.cabeza.sig = None
self.cabeza = siguiente
def main():
lista = ListaEnlazada()
for valor in (30, 20, 10):
lista.insertar_inicio(valor)
print("Lista actual:")
lista.imprimir()
lista.limpiar()
if __name__ == "__main__":
main()
# listas_enlazadas.pyinsertar_iniciosig con la antigua cabeza y actualiza el estado interno.imprimirlimpiarif __name__ == "__main__":