¿Qué son los nodos y cómo se utilizan en estructuras de datos más complejas?
Los nodos son componentes fundamentales para la creación de estructuras de datos complejas y más elaboradas. Cada nodo almacena un valor o dato y contiene un puntero que dirige hacia otro nodo. Este tipo de estructura es invaluable cuando se maneja información dispersa en memoria y las limitaciones de espacio impiden disponer los datos de forma continua. Vamos a examinar cómo se crean y utilizan nodos en programación.
¿Cómo crear una clase nodo en Python?
Comencemos creando una clase llamada Node. La definición de la clase incluye un constructor __init__ con atributos clave: data y next. El atributo data almacenará el valor del nodo, y next indicará a qué nodo apunta, con un valor predeterminado de None para el último nodo.
classNode:def__init__(self, data,next=None): self.data = data
self.next=next
¿Cómo instanciar y enlazar nodos?
Una vez definida la clase, podemos instanciar nodos e interconectarlos. Veamos un ejemplo de cómo funciona esto:
Creamos varios nodos:
node1 sin valor inicial (None).
node2 con un valor de dato 'a' que no apunta a otro nodo.
node3 con un valor de dato 'b' que apunta al node2.
Para visualizar los datos o las referencias de un nodo, empleamos sus atributos.
node1 = Node(None)node2 = Node('a')node3 = Node('b', node2)# Mostrar datos y referenciasprint(node2.data)# Output: aprint(node2.next)# Output: Noneprint(node3.next.data)# Output: a
¿Cómo modificar las referencias de los nodos?
Podemos modificar las referencias de un nodo para que apunten a otros nodos. Esto es útil cuando deseamos reorganizar la estructura de datos.
node1.next= Node('c', node3)print(node1.next.data)# Output: bprint(node1.data)# Output: c
¿Cómo crear una serie de nodos usando un ciclo?
Una práctica común es usar bucles para generar una serie secuencial de nodos. Veamos cómo crear una lista de nodos con un ciclo for:
head =Nonefor count inrange(1,5): head = Node(count, head)current = head
while current:print(current.data) current = current.next
Esta estructura recorrerá nodos desde el final al primero, mostrando los números en orden inverso.
¿Cuáles son las ventajas de usar nodos?
Flexibilidad: Los nodos permiten referencias no secuenciales, adaptándose a estructuras de datos no continuas.
Dinamismo: Se pueden añadir o eliminar nodos sin reestructurar memoria contigua.
Eficiencia: Facilitan la gestión de memoria para datos dispersos.
Por supuesto, dominar los nodos y las series conectadas como Linked lists enriquece tu capacidad para manejar datos de manera eficiente y eficaz. Experimentar con ciclos, iteraciones y referencias ofrece una gran profundidad en el manejo de estructuras complejas. Continúa explorando y aplicando estas técnicas: el entendimiento y facilidad para trabajar con nodos solo mejorará con la práctica.
Y con esto queda claro como funciona una queue o un stack :o
Hasta ahora, que curso tan bueno :D
jajajaja es sarcasmo, cierto??
Resumen:
■■■■■■■
Crear Nodos.
Cada nodo almacenará un valor y cada nodo tiene un puntero que llevará a otro nodo con otro valor y así obtener los datos allí almacenados.
Es muy útil al tener infromación dispersa en memoria y cuando queremos que sean consultas ágiles, es importante entender que los nodos son la base para implementaciones más elaboradas de estructuras de datos, Stacks, Qeues, Deque, Doubly, Singly List, Circular list, Graphs .
Cada estructura de datos servirá para un propósito dentro de un contexto, por ejemplo los grafos acíclicos, donde se usan para sistemas de recomendaciones al mostrar las relaciones entre objetos o representar los tipos de redes que se forman entre nodos. Para crear un nodo:
Creamos una clase Node
Referimos valores mediante argumentos de instancias.
Unimos los nodos iterando entre referencias.
Este script tiene como propósito crear nodos.
Constructor:
data= El dato del nodo.
next= está por defecto en None, porque en una serie de nodos el +ultimo te lleva a ninguna parte
from node importNodenode1 =Nonenode2 =Node("A",None)# Los nodos al ser secuanciales permiten refrencias a cualquier lugar.node3=Node("B", node2)def show_relations():'''
Este script perfectamente puede ser una función que recibe al nodo como parámetro pythony llamo para mostrar las
relaciones.'''
print("Esto es la ubicación en memoria de los nodos")print(node2) #<node.Node object at 0x0000022017FEAD30>print(node3) #<node.Node object at 0x0000022017FEAD90>print("Esto es el dato y muestra la relación entre nodos")print(node2.data,"-->", node2.next) #A-->Noneprint(node3.data,"-->", node3.next) #B--><node.Node object at 0x0000015356D6AD30>print("El siguiente dato del nodo es:") # Se refiere al nodo que está conectado y luego al dato que este contiene.print(node3.next.data) #'A'print("Creando el nodo1 y mostrando datos y relacion con nodo3 obtenemos: ") # Asignar una propiedad a un elemento para volverlo nodo. # Al intanciar la clase con una relación estamos ligando los nodos.node1=Node("C", node3)print(node1.data,"-->", node1.next)#C--><node.Node object at 0x0000022017FEAD90>def create_nodes():print("Creo nodos que se asignan a un solo valor en memoria, en este caso a node2: ")for node inrange(5): #n --><node.Node object at 0x000001B2AD991FD0> head =Node(node, node2)print(head.data,"-->", head.next)def run():show_relations()create_nodes()if __name__ =='__main__':run()
Excelente Aporte :thumbsup:
Mil gracias, la verdad no entendí en su totalidad la clase del profesor, pero tu artículo me ayudó a entender mas.
Una forma mas clara de ver la composicion de los nodos es usando dataclasses, que son una forma un poco mas sencilla de declarar clases en Python :
Data Classes in Python 3.7+ (Guide)
++Clase Node++:
++Main++:
++Output++:
Gracias!
Wooow excelente manera de ver los nodos con las dataclasses
Pienso que el for debio haber sido
for count in range(1,6)
para que coincida con la deapositiva de crear 5 nodos.
Recuerden que en la función range, el segundo valor es exclusivo, por lo que no es agregado.
Aún me parece extraño que aunque se sobreescriba la variable head para crear otro nodo, esta siga guardando la referencia en memoria del anterior nodo creado.
Si te refieres a esta parte head = Node(count, head) se podría ver de esta manera i = i + 1 (primero incrementa luego lo sobreescribe) en este caso crea un nuevo nodo con el head del ciclo anterior luego lo sobreescribe (nuevo nodo). Y bueno para el while el head de inicio vendría a ser el nodo creado en el ultimo ciclo.
Gracias por tu aporte.
De esta manera dejé mi clase de Nodo con tipado estático para que fuese más claro.
from __future__ importannotationsfrom typing importAnyclassNode(): def __init__(self,data:Any,next:Node=None)->None: self.data= data
self.next= next
Gracias por tu aporte.
Que lastima que un tema tan interesante esté dictado de una manera tan confusa. Para entender este curso me tocó estudiar por mi cuenta en otras páginas por meses y aún así se hace difícil de seguir el proceso lógico que sigue el docente.
Ojalá lo renueven con otro docente, todos los cursos que he visto de Héctor Vega son igual de malos en cuanto a pedagogía.
Estoy de acuerdo. No reconoce que nosotros no tenemos el dominio de él en ciertas instrucciones computacionales, como lo recursivo, que cuesta tiempo madurarlo... si evitara esa recursividad, o al menos no usara dos instrucciones recursivas de un plumazo, daría una confusión menor.
En el primer for asigna al head.next el valor de head: al head recién creado, la asigna el valor del head pasado. ¿por qué? porque primero opera la definición del node, a la derecha del paréntesis, donde 'head' es el nodo de la iteración anterior, y luego asigna al 'head' nombre head el nuevo nodo.
Más fácil decir
Node0 = Node
Node1 = Node(1,Node0)
Nodo2 = Node(2,Node1)...
como al final del for terminamos con head asignado al node con data = 4, el while parte del nodo4, recorriendo hacia atrás hasta llegar a null
que triste que dejen estos cursos así tan bien que iba con fundamentos, y comprenhentios errores funciones de Matías
Siendo un poco explícito, hicimos algo así:
#from node importNodeclassNode(): def __init__(self, data, next=None): self.data= data
self.next= next
node1 =Nonenode2 =Node("A",None)node3 =Node("B", node2)node1 =Node("C", node3)head =Noneprint(node2) # <__main__.Node object at 0x7fc842dd6e90>print(node2.data) # Aprint(node2.next) # Noneprint(node3.next) # <__main__.Node object at 0x7f241fc96e90>print(node3.next.data) # Aprint(node1.next) # AttributeError:'NoneType' object has no attribute 'next'print(node1.next.data) # B<= porque pusimos en la linea 14 que ahora el node1 apunta a node3
print(node1.data) # Cfor count inrange(1,5): head =Node(count, head)while head !=None:print(head.data) head = head.next#4#3#2#1# aqui creo que es donde se cumple lo de n-1
Sobre todo en el video anterior y al comienzo de este siento que me distrae mucho, mientras explica datos importantes, cuando mira hacia arriba o a su derecha (si es un telepronter o la pantalla) genial que a futuro actualicen estas clases que están muy interesantes ubicando la pantalla justo en frente o de algún ángulo que no se note tanto el cambio de vista. Gracias.
increíble que nunca se equivoca, como si lo estuviera copiando jejeje
Escribi este codigo para imprimir las conexiones entre nodos
from node import Node
head =Nonefor letter in"ABCDEFG": head = Node(letter, head)while head !=None:print(f"{head.data} -> {head.next.data if head.nextelseNone}") head = head.next
Esto imprime
G -> F
F -> E
E -> D
D -> C
C -> B
B -> A
A -> None
Una pregunta.... esto aprendía en su momento a realizarlo en C, claro, los argumentos eran claramente diferenciados porque uno era una variable (un dato) y el otro era un puntero ( una dirección de memoria ) por lo que quedaba más claro, desde mi punto de vista...
Aquí los dos argumentos son variables, esto porque es? porque en python siempre se pasa valores por referencia?
Les comparto mi código, deseo que les sea de utilidad:
classNode():def__init__(self, data,next=None):# hacemos referencia None, porque el ultimo nodo te va a llevar a None self.data = data
self.next=nextif __name__ =='__main__':from node import Node
node_1 =None node_2 = Node('A',None) node_3 = Node('B', node_2)print(node_2)print(node_2.data)print(node_2.next)print(node_3.next)# para ver el dato del siguiente nodoprint(node_3.next.data)# para que el nodo_1 sin valor, lo apuntaramos a otro lado o quisieramos saber a donde apunta node_1 = Node('C', node_3)print(node_1.next.data)# Bprint(node_1.data)# C# asi podemos ver como utilizamos esos nodos# vamos a crear una serie de nodos head =Noneprint('Esta es la nueva linked list')for count inrange(1,5): head = Node(count, head)print(head.data)print(head)'''
Esta es la nueva linked list
1
<node.Node object at 0x7f850db67be0>
2
<node.Node object at 0x7f850db67b50>
3
<node.Node object at 0x7f850db67af0>
4
<node.Node object at 0x7f850db67a60>
''''''
print('aqui empieza una iteracion')
while head != None:
print(head.data)
head = head.next
''''''
4
3
2
1
'''
classNode(): def __init__(self, data, next=None):"""Initialize this node with the given data.""" self.data= data
self.next= next
if __name__ =="__main__": node1 =None node2 =Node("A",None) node3 =Node("B", node2) node1 =Node("C", node3)print(f'Impresion de ref memoria del nodo2: {node2}')print(f'Impresion de la data del nodo2: {node2.data}')print(f'Impresion siguiente de ref memoria nodo3,es decir ref memoria nodo2: {node3.next}')print(f'Impresion de la data siguiente del nodo3,es decir nodo2:{node3.next.data}') head =None #se define head con valores vacios
print("\n\n")for count inrange(1,5): head =Node(count, head) #Como datos se ingresa, la cuenta de bucle y apunta al siguiente valor de la cuenta
while head !=None: #Como head ya no esta vacio por el bucle for anterior se ejecuta
print(head.data) #Se imprime la data cada valor de head
head = head.next #Y se apunta al siguiente valor
No estoy entendiando ni papas
youtube_com/watch?v=qk67wS7WYxo
con esté video lo entendí mejor y eso que los explica con JS y no se nada de JS
classNode(): def __init__(self, data, next =0): self.data= data
self.next= next
Recuerda que en una lista enlazada, los nodos se enlazan estableciendo la referencia next del nodo actual para que apunte al siguiente nodo en la secuencia. En este ejemplo, los nodos aún no están enlazados. Si deseas enlazarlos para formar una lista enlazada, necesitarás establecer las referencias next adecuadamente.
Al hacer la práctica se entendió mejor, la evolución de como el código llega hasta allá y por qué. Gracias también a los compañeros por sus buenos aportes, que sería de platzi sin la comunidad.
Por si no se entendió lo que está sucediendo... Al inicializar cada nodo dentro de la misma variable, pero hacer referencia en el atributo "next" a la anterior, es como si estuviesemos cargando un nodo dentro del otro al estilo "Mamushka rusa". En una sola variable del tipo Node tenemos en realidad 4 nodos juntos. Una vez que recorremos mediante el metodo next cada uno de esos nodos la variable se "vacia" y vuelve a ser del tipo "None" como era al principio.
Les dejo el codigo con todo el último ejemplo del profe pero mas explicado y con output incluido dentro del mismo para mayor detalle. Cualquier duda a disposición:
from node importNodehead =Noneprint("Antes del for: ")print(f"head es de tipo: {type(head)}")# Estoy metiendo un nodo adentro del otro.Es fantastico...Todos quedan guardados dentro de head:for count inrange(1,5): head =Node(count, head)print("Despues del for pero antes del while: ")print(f"head es de tipo: {type(head)}")# Imprimo todos los nodos anidados uno dentro del otro:while head !=None:print(f"head.data: {head.data}")print(f"head.next: {head.next}")try:print(f"head.next.data: {head.next.data}") except AttributeErroraserror:print(error) head = head.next# Al recorrer el while hasta el None vacío mi nodo y deja de ser una variable tipo nodo para ser nuevamente una variable tipo Noneprint("Despues del while: ")print(f"head es de tipo: {type(head)}")"""
Output:22:21:24 👽 with 🤖 mgobea 🐶 in develop/python/data_structs_python …
➜ python3 nodos_2.pyAntes del for:head es de tipo:<class'NoneType'>Despues del for pero antes del while:head es de tipo:<class'node.Node'>head.data:4head.next:<node.Node object at 0x7f4135e40640>head.next.data:3head.data:3head.next:<node.Node object at 0x7f4135e40490>head.next.data:2head.data:2head.next:<node.Node object at 0x7f4135e406d0>head.next.data:1head.data:1head.next:None'NoneType' object has no attribute 'data'Despues del while:head es de tipo:<class'NoneType'>"""
Para el profesor las formulas recursivas están internalizadas (es ‘recurrente’ en utilizarlas) y jura que nosotros también somos igual que él. Intentaré un código que le quita un grado de recursividad y con eso se hace más compresible (al menos para mí)
Al menos a mí me ayudó…
“NodoActual = NodoActual.next” opera primero obteniendo el Nodo next a la derecha del paréntesis (el Nodo4.next→ Nodo3), y luego reasignando el valor NodoActual al nodo recién referenciado (Nodo3)… de la misma forma en que al ejecutar “i = i - 1”, se reduce el valor actual de i, como instrucción a la derecha del =, y luego de reasigna el valor de i.
En el ultimo ejemplo puede confundir el hecho de que no se asigna a todos los nodos un nombre de variable propio.
El unico nodo del que si tenemos un nombre seria el head, cuyo valor asignado quedo el numero 4 (al cual podemos acceder directamente con head.data como en el ejemplo anterior).
Y este si tiene un nodo de referencia que seria el nodo que tiene como valor asignado al numero 3, y al cual podemos acceder con head.next con lo que nos arrojara de hecho su valor en memoria.
Pero a este ultimo nodo mencionado solo podemos acceder a traves del nodo anterior, el de nombre head, necesitando para acceder, por ejemplo, a su valor el comando head.next.data.
Lo mismo sucede con los siguientes nodos. Mi pregunta es si estos tambien tienen un nombre de variable asignado o simplemente se les llama a traves del head? Lo que me da tranquilidad de que existen por separado, es que en realidad si podemos ver que si tienen un valor en memoria diferente y lo podemos ver imprimiendo, por ejemplo, en el ciclo while, en vez de print(head.data), escribir simplemente print(head):
while head !=None:print(head) head = head.next
dando como resultado, en mi caso:
# <node.Node object at 0x7fa93eae6410># <node.Node object at 0x7fa93eae4280># <node.Node object at 0x7fa93eae40d0># <node.Node object at 0x7fa93eae4310>