Aprende a insertar un nodo en la parte intermedia de una lista enlazada en JavaScript sin romper referencias. Con una lógica clara y segura frente al garbage collector, verás cómo usar pointers, identificar el índice previo y encadenar correctamente next para mantener la estructura íntegra.
¿Cómo insertar un nodo intermedio en JavaScript?
Insertar en medio no significa “exactamente al centro”, puede ser después de la head o antes de la cola. Lo clave es no dejar ningún nodo existente sin un pointer, porque el garbage collector podría eliminarlo.
Todas las soluciones válidas cuentan. Si tu lógica es distinta y el resultado es correcto, está bien.
Secuencia segura. Localiza el nodo previo (índice − 1), guarda su siguiente en un holding pointer, enlaza el previo con el nuevo nodo y conecta el nuevo con el holding pointer.
Actualiza la longitud. Aumenta la longitud y retorna la lista.
Ejemplo de implementación basada en la explicación:
classLinkedList{constructor(){this.head=null;this.length=0;}append(value){// Agrega al final. (Implementado previamente en tu estructura.)}insert(index, value){// Validación: si el índice no existe, insertar al final.if(index >=this.length){console.log('No hay suficientes elementos, se inserta al final.');returnthis.append(value);}const newNode =newNode(value);// firstPointer: nodo previo al índice objetivo.const firstPointer =this.getIndex(index -1);// holdingPointer: el que “espera” para no perderse.const holdingPointer = firstPointer.next;// Reasignaciones seguras para no perder referencias. firstPointer.next= newNode; newNode.next= holdingPointer;this.length++;returnthis;}getIndex(index){let counter =0;let currentNode =this.head;// Siempre se inicia desde la head.while(counter !== index){ currentNode = currentNode.next; counter++;}return currentNode;}}
¿Por qué importa el garbage collector y los pointers?
Si reasignas antes de tiempo, un nodo puede quedar sin pointer. Cuando eso pasa, el garbage collector lo elimina para liberar memoria. Por eso se usa un holding pointer: guarda temporalmente el enlace que aún necesitas.
No rompas el enlace existente sin respaldo. Primero guarda el next actual.
Evita “huérfanos”. Todo nodo debe quedar accesible por al menos un pointer.
Orden importa. Reasignar en desorden provoca pérdidas de referencias.
¿Cuál es la secuencia correcta de reasignación de next?
Localiza el previo con getIndex(index - 1).
Guarda su siguiente en un holding pointer.
Asigna previo.next = nuevoNodo.
Asigna nuevoNodo.next = holdingPointer.
Incrementa la longitud y retorna la lista.
¿Qué validaciones iniciales evitan errores de índice?
Validar índices y casos límite evita resultados inesperados.
Índice fuera de rango. Si index ≥ longitud, se inserta con append al final.
Retro de práctica. Valida listas vacías y entradas inválidas para fortalecer tu método.
Mensajes claros. Un simple console.log ayuda a entender qué ocurrió.
Ejemplo mínimo de la validación usada arriba:
if(index >=this.length){console.log('No hay suficientes elementos, se inserta al final.');returnthis.append(value);}
¿Cómo recorrer la lista para ubicar el índice previo?
Para mover punteros con seguridad, necesitas el nodo previo al objetivo. Se recorre desde la head con un contador hasta que counter === index.
Punto de partida. El current node inicia en la head.
Recorrido lineal. Avanza con current = current.next e incrementa el contador.
Devolución precisa. Cuando el contador coincide, retorna ese nodo.
Fragmento clave:
getIndex(index){let counter =0;let currentNode =this.head;while(counter !== index){ currentNode = currentNode.next; counter++;}return currentNode;// Nodo en la posición solicitada.}
¿Listo para el siguiente paso? Implementa tu método remove para sacar un nodo intermedio y, si quieres moverlo al final, vuelve a usar append. Comparte tu solución y cuéntame cómo lo resolviste.
Les dejo un diagrama que hice en mi pizarron la verdad si estuvo confuso pero ya lo entendi espero te ayude :D
te comparto mi codigo por si te fallo algo
classLinkedList{// AL INSTANCIAR LA CLASE SE EJECUTA EL BOB CONTRUCTOR// BOB EL CONSTRUCTOR RECIBE UN VALORconstructor(val){// LA CABEZA DE LA LISTA APUNTA AL VALOR// QUE NOS MANDARONthis.head={value: val,next:null// EL SIGUIEN APUNTA A NADA}// LA COLA APUNTA A LA CABEZERAthis.tail=this.head// EL LARGO INICIA EN 1this.length=1}append(val){// INSTANCIAMOS EL NODO const newNode =newNodo(val)// ANIDA AL HEAD EL NUEVO NODOthis.tail.next= newNode
// GUARDAMOS EL NUEVO NODO EN LA COLAthis.tail= newNode
// AUMENTAMOS LA LISTA EN 1this.length++console.log(this);}prepend(val){// GENERAMOS UN NUEVO NODOconst newNode =newNodo(val)// SU NEXT APUNTA A LA CABEZA newNode.next=this.head// LA CABEZA ES EL NUEVO NODO CON LOS DATOS ANTERIORESthis.head= newNode
// EL LARGO AUMENTAthis.length++returnthis}insert(index , val){// SI EL INDEX QUE ME PASAN ES MAYOR// AL TAMAÑO DE MI LISTAif( index >=this.length){// AÑADE AL FINAL DE LA LISTA// EL NUEVO VALORreturnthis.append(val)}// CREAMOS UN NUEVO NODOconst newNode =newNodo(val)const firstPointer =this.getTheIndex( index -1)const holdingPointer = firstPointer.next firstPointer.next= newNode
newNode.next= holdingPointer
}getTheIndex(i){let counter =0let currentNode =this.headwhile( counter != i ){ currentNode = currentNode.next counter++}return currentNode
}}const list =newLinkedList(1)
Súper, solo te faltó el lenght++ en el insert, de resto perfect :)
Gracias, sinceramente me parece la solución mas clara de entender
¡Bro! Excelente aporte. Yo también llegué a la misma conclusión, pero este código tiene un problema y es de que si borras el primer elemento, a como esta ese codigo, da un error, pues previousPointer no existe.
remove(index){if(index >=this.length){console.error("index is out of limits of the array");}elseif( index ==0){this.head=this.head.next;this.length--}elseif(index ===this.length-1){const firstPointer =this.getTheIndex(index -1); firstPointer.next=null;this.tail= firstPointer;this.length--;}else{const firstPointer =this.getTheIndex(index -1);const pointerToRemove = firstPointer.next; firstPointer.next= pointerToRemove.next;this.length--;}}```
👨💻👩💻 Mi solución con los metodos Delete, Pop y shift
classNode{constructor(value){this.value= value;this.next=null;}}classMySinglyLinkedList{constructor(value){// creamos el inicio de nuestro SinglyLinkedListthis.head={ value,next:null,};// Aqui sucede la magia ✨// Todo lo que modifiquemos en tail// se modificara en la estructura inicialthis.tail=this.head;this.length=1;}append(value){// aqui estamos creando un nuevo nodoconst newNode =newNode(value);// Como mencionamos anteriormente// si modificamos la cola por la REFERENCIA// se modificara la estructura inicial!this.tail.next= newNode;// Pero aun tail sigue apuntando a la CABEZA// de la estructura inicial entonces es momento// de apuntar al nuevo nodo creadothis.tail= newNode;this.length++;returnthis;}prepend(value){const newNode =newNode(value); newNode.next=this.head;this.head= newNode;this.length++;returnthis;}insert(index, value){if(index >=this.length){returnthis.append(value);}const newNode =newNode(value);const firstPointer =this.getTheIndex(index -1);const holdingPointer = firstPointer.next; firstPointer.next= newNode; newNode.next= holdingPointer;this.length++;returnthis;}getTheIndex(index){let counter =0;let currentNode =this.head;while(counter !== index){ currentNode = currentNode.next; counter++;}return currentNode;}delete(index){if(index <=0)returnthis.shift();if(index >=this.length)returnthis.pop();const prevDeletedNode =this.getTheIndex(index -1); prevDeletedNode.next= prevDeletedNode.next.next;this.length--;returnthis;}shift(){const secondNode =this.head.next;this.head= secondNode;this.length--;returnthis;}pop(){const prevLastNode =this.getTheIndex(this.length-2);this.tail= prevLastNode; prevLastNode.next=null;this.length--;returnthis;}}
Todo bien con el shift, pero deberia retornar el elemento eliminado.
Encontré un bug en el código de @degranda:
.
Si pones el índice "0" al método insert te salta un error:
myLinkedList.insert(0,"Esta línea dará error");
Aquí estoy usando el código que subió a la sección de archivos y enlaces:
También me pasa lo mismo
No es un bug, de hecho Diego lo menciona cuando empieza a codear y nos aclara que es un reto para nosotros hacer validaciones para evitar esos errores.
Y pues ese se corrige fácilmente poniendo
if(position ===0){returnthis.prepend(value);}
Mi solucion para remove:
remove(index){if(index >this.length|| index <0){console.error("Index of bounds");}const holdingNode =this.getTheIndex(index-1);if(index ===0){this.head= holdingNode.next;}else{const removedNode = holdingNode.next; holdingNode.next= removedNode.next;}returnthis;}
Esta excelente tu codigo, solo te falto el this.length-- antes del return.this
Yo tengo una duda con respecto al elemento que estamos "borrando" cuando usamos el método remove().
En la imagen de arriba está la lógica que seguí para implementar este método. En ningún momento borro el elemento como tal, simplemente hago que el next del elemento 2 pase a ser el elemento 3. Es decir, estoy haciendo una especie de puente entre ambos números. Pero el elemento 5 sigue apuntando al elemento 3.
Mi pregunta es, ¿realmente el garbage collector eliminaría el elemento 5 de la memoria, o lo seguiría manteniendo ya que sigue apuntando al elemento 3?
Estoy consciente que, debido al principio de funcionamiento que de la singly Linked List ya no se podría acceder al elemento 5, ¿pero quedaría almacenado en memoria o se eliminaría?
Debes tener en cuenta que en el GC sólo las "incoming references" son las que cuentan para mantener un objeto en memoria, no las "outgoing references".
En este caso 5 ya no tiene ninguna "incoming reference" por lo tanto pasa a ser un candidato para ser recolectado cuando el GC se ejecute. La "outgoing reference" que tiene hacia 3 no le importa al GC.
Reto logrado
El método que hice hace uso del método getTheIndex(index) que el profe hizo para poder obtener el index del item de la lsita que deseo eliminar.
remove(index){if(index >=this.length){return"This action is not possible because your index is higher to list length";}let nextToRemoveItem =this.getTheIndex(index +1);let beforeToRemoveItem =this.getTheIndex(index -1); beforeToRemoveItem.next= nextToRemoveItem;this.length--;returnthis;}
Nunca pares de aprender 💚
Mil años después pero es el primer código que le puse atención fue de forma random.
Por lo que veo que veo falto mover tail, segundo como eliminas el primer índex y el ultimo.
Here is my code:
.
classNode{constructor(value){this.value= value;this.next=null;}}classLinkedList{constructor(value){this.head=newNode(value);this.tail=this.head;this.length=1;}append(value){let node =newNode(value);this.tail.next= node;this.tail= node;this.length++;}prepend(value){let node =newNode(value); node.next=this.head;this.head= node;this.length++;}get(index){let pointer =this.head;let count =0;while(pointer !=null){if(count === index)return pointer; pointer = pointer.next; count++;}returnnull;}insert(value, index){if(index ===0)returnthis.prepend(value);if(index >=this.length)returnthis.append(value);if(index >0&& index <this.length){const prevPointer =this.get(index -1);if(prevPointer){const nextPointer = prevPointer.next;const node =newNode(value); node.next= nextPointer; prevPointer.next= node;this.length++;return;}}console.log("index out of bounds: "+ index);}remove(index){if(index ===0){this.head=this.head.next;this.length--;return;}if(index >0&& index <this.length){const prevPointer =this.get(index -1);if(prevPointer){const pointer = prevPointer.next;if(pointer) prevPointer.next= pointer.next;if(pointer ===this.tail)this.tail= prevPointer;this.length--;return;}}console.log("index out of bounds: "+ index);}toString(){let pointer =this.head;let s ="";while(pointer !=null){ s += pointer ===this.head?"(H: ": pointer ===this.tail?"(T: ":"(N: "; s += pointer.value+") -> "; pointer = pointer.next;} s +="NULL | length = "+this.length;console.log(s);}}
.
Test:
.
let ll =newLinkedList(1);ll.append(2);ll.append(3);ll.append(4);ll.prepend(0);ll.toString();let index =3;let value =":D";console.log("Insert "+ value +" at "+ index);ll.insert(value, index);ll.toString();index = ll.length+8;value ="Y";console.log("Insert "+ value +" at "+ index);ll.insert(value, index);ll.toString();index =1;value ="z";console.log("Insert "+ value +" at "+ index);ll.insert(value, index);ll.toString();index =-1;value ="c";console.log("Insert "+ value +" at "+ index);ll.insert(value, index);ll.toString();index =0;value ="A";console.log("Insert "+ value +" at "+ index);ll.insert(value, index);ll.toString();index = ll.length;value ="O";console.log("Insert "+ value +" at "+ index);ll.insert(value, index);ll.toString();index =4;console.log("Remove "+ index);ll.remove(index);ll.toString();index = ll.length-1;console.log("Remove "+ index);ll.remove(index);ll.toString();index =0;console.log("Remove "+ index);ll.remove(index);ll.toString();index =1;console.log("Remove "+ index);ll.remove(index);ll.toString();index = ll.length;console.log("Remove "+ index);ll.remove(index);ll.toString();
.
Output:
.
(H:0)->(N:1)->(N:2)->(N:3)->(T:4)->NULL| length =5Insert:D at 3(H:0)->(N:1)->(N:2)->(N::D)->(N:3)->(T:4)->NULL| length =6InsertY at 14(H:0)->(N:1)->(N:2)->(N::D)->(N:3)->(N:4)->(T:Y)->NULL| length =7Insert z at 1(H:0)->(N: z)->(N:1)->(N:2)->(N::D)->(N:3)->(N:4)->(T:Y)->NULL| length =8Insert c at -1index out ofbounds:-1(H:0)->(N: z)->(N:1)->(N:2)->(N::D)->(N:3)->(N:4)->(T:Y)->NULL| length =8InsertA at 0(H:A)->(N:0)->(N: z)->(N:1)->(N:2)->(N::D)->(N:3)->(N:4)->(T:Y)->NULL| length =9InsertO at 9(H:A)->(N:0)->(N: z)->(N:1)->(N:2)->(N::D)->(N:3)->(N:4)->(N:Y)->(T:O)->NULL| length =10Remove4(H:A)->(N:0)->(N: z)->(N:1)->(N::D)->(N:3)->(N:4)->(N:Y)->(T:O)->NULL| length =9Remove8(H:A)->(N:0)->(N: z)->(N:1)->(N::D)->(N:3)->(N:4)->(T:Y)->NULL| length =8Remove0(H:0)->(N: z)->(N:1)->(N::D)->(N:3)->(N:4)->(T:Y)->NULL| length =7Remove1(H:0)->(N:1)->(N::D)->(N:3)->(N:4)->(T:Y)->NULL| length =6Remove6index out ofbounds:6(H:0)->(N:1)->(N::D)->(N:3)->(N:4)->(T:Y)->NULL| length =6
Hola, logre que funcionen, pero buando borro un valor del medio de la lista, la misma se vidide en 2, conservando dentro de la variable una segunda rama del array. o eso me parece que pasa. que esta mal ?
delete(index){//no tiene nada cargado salirif(index ==null){return;}//borramos la cabezaif(index ==0){this.head=this.head.next;// cambio la cabeza y ya.this.length--;console.log("Se elimino el ultimo elemento primer elemento de la lista")returnthis;}//el index es mayor o igual a la longitud del linkedlistif(index >=this.length){const newNext =this.getTheIndex(index -1);const lastNext =this.getTheIndex(index -2);const newEnd =newNode(newNext); lastNext.next= newEnd;this.tail= newEnd;this.length--;console.log("Se elimino el ultimo elemento de la lista")returnthis;}//------- falta en momento en que meto el nuevo pointer a la listaconst lastNode =this.getTheIndex(index -2);// - 1const newNext =this.getTheIndex(index +1)// - 3const newNode =newNode(this.getTheIndex(index -1));// nuevo pointer - 5//diagrama - 1 -> 5 -> -8- -> 3 lastNode.next= newNode; newNode.next= newNext;this.length--;returnthis;}
Hola gente, me volvi un poco loco buscando un error que me agregaba el insert en el lugar correcto, pero ademas me lo añadía al final, y resulta que en el primer if no le habia puesto el this, al this.length.
Alguien me podria decir porque JS retorna true cuando se compara un numero con length a secas (sin haberlo definido a length como una variable ni nada).
Osea, (2 > length) --> true ????
Hola, tengo una duda: por qué es necesario el metodo getTheIndex?
Hola Valeria, el método "getTheIndex" está devolviendo el objeto en el índice indicado. O sea si se le pasa como argumento 3, devolverá el 4to objeto anidado de nuestra Singly Linked List.
Esto tranquilamente se podría resolver con código dentro de nuestro "insert" sin la necesidad de crear el método, pero el profesor lo está creando y dejando ya disponible, porque cuando por fuera de la función insert deseemos que se nos devuelva el objeto que se encuentra en algún índice específico, podremos llamar a getTheIndex sin necesidad de volver a escribir el código, e incluso si alguna otra función (como la de delete) llegase a necesitar buscar un índice también, podría llamar directamente a getTheIndex.
Solución al reto
No se si entendi muy bien, si el metodo remove coge un nodo y lo pone al final del linked list, o si coge un nodo y lo ubica segun el indice que se le especifica. Igual lo hice de la primera forma que describí. 🧰⚔
remove(index, value){//Rehubica un nodo al final del linkconst firstPointer =this.getTheIndex(index -1);const secondPointer =this.getTheIndex(index +1); firstPointer.next= secondPointer;this.append(value);this.legnth--;returnthis;}
Hola! está bien, no importa cómo lo resuelvas si obtienes el mismo resultado. Peeero dejame hacerte unas observaciones.
No es necesario que le pases el value si ya le pasas el index, remove() solo debería eliminar un nodo. ¿Y si el usuario solo quiere eliminarlo y ya?
Tampoco es necesario que vuelvas a llamar al método getTheIndex para el segundo nodo, porque estás llamando a la función, la función se ejecuta, se ejecuta el ciclo que está dentro y eso es ineficiente cuando ya tienes el anterior valor como firstPointer y puedes acceder al secondPointer con firstPointer.next.next.
Y te pregunto, ¿qué pasaría si el índice que le pasas es 0 (el primer nodo)?¿y si pasas un índice negativo?¿ y si pasas un índice mayor al length, debería eliminarlo o no?
Mi forma de hacer el remove
No sé si sea la más óptima, pero funciona bastante bien.
remove(index){if(index >=this.length){console.log('Wrong index');returnnull;}if(index ===0){this.head=this.head.next;}else{let firstPart =this.head;// Llego hasta el extremo de la primera partefor(let i =0; i < index -1; i +=1){ firstPart = firstPart.next;}// Borro el enlace firstPart.next= firstPart.next.next;}this.length-=1;returnnull;}
Estructuras de Datos fue una materia difícil de pasar en la universidad jaja.
Si no le entienden a la primera, es totalmente normal, a veces estos nuevos temas les rompen la cabeza, pero no se desanimen, con paciencia se puede aprender muy bien estos conceptos. ✅
Vengo de aprender JavaScript con prototipos, me costo mucho entender los linkedlist y arboles, la logica en objetos y prototipos es muy diferente.
Retos cumplidos
insert(index, value){if(index >=this.length){this.append(value);}elseif(index ===0){this.prepend(value);}else{const newNode =newNode(value);let target =this.head;let slice =undefined;for(let i =1; i < index; i++){ target = target.next;} slice = target.next; target.next= newNode; target.next.next= slice;this.length++;returnconsole.log(this.head);}}remove(index){if(index >=this.length){returnconsole.log("Índice no Válido")}else{let target =this.head;let slice =undefined;for(let i =1; i < index; i++){ target = target.next;} slice = target.next.next; target.next= slice;this.length--;returnconsole.log(this.head);}}```
Dejo mi resolucion del reto
remove(index){// eliminar headif(index ===0){this.head=this.head.next// si la lista queda vacíaif(this.length===1){this.tail=null}this.length--returnthis}if(index <0|| index >=this.length){console.log(`Indice: ${index} inexistente, solamente tienes ${this.length-1} indices.`)return}let current =this.headfor(let i =0; i < index -1; i++){ current = current.next}const prev = current
const nodeToRemove = current.nextconst next = nodeToRemove.nextif(index ===this.length-1){ prev.next=nullthis.tail= prev
}else{ prev.next= next
}this.length--returnthis;}
Esta es mi solución, es de O(2n) ya que recorreríamos la lista dos veces. Una para capturar el nodo en la posición y otra para agregarlo junto con el nuevo nodo.