Buscar..


Introducción a las listas enlazadas

Una lista enlazada es una colección lineal de elementos de datos, llamados nodos, que están vinculados a otro (s) nodo (s) mediante un "puntero". A continuación se muestra una lista enlazada individualmente con una referencia principal.

         ┌─────────┬─────────┐   ┌─────────┬─────────┐         
 HEAD ──▶│  data   │"pointer"│──▶│  data   │"pointer"│──▶ null 
         └─────────┴─────────┘   └─────────┴─────────┘         

Hay muchos tipos de listas enlazadas, entre ellos por separado y doblemente listas enlazadas y listas enlazadas circulares.

Ventajas

  • Las listas enlazadas son una estructura de datos dinámica, que puede aumentar y disminuir, asignando y desasignando memoria mientras el programa se está ejecutando.

  • Las operaciones de inserción y eliminación de nodos se implementan fácilmente en una lista enlazada.

  • Las estructuras de datos lineales, como pilas y colas, se implementan fácilmente con una lista vinculada.

  • Las listas enlazadas pueden reducir el tiempo de acceso y pueden expandirse en tiempo real sin sobrecarga de memoria.

Lista enlazada individualmente

Las listas enlazadas individuales son un tipo de lista enlazada . Los nodos de una lista enlazada individualmente solo tienen un "puntero" a otro nodo, generalmente "siguiente". Se denomina lista enlazada individualmente porque cada nodo solo tiene un "puntero" a otro nodo. Una lista enlazada individualmente puede tener una referencia de cabecera y / o cola. La ventaja de tener una referencia de cola son los getFromBack , addToBack y removeFromBack , que se convierten en O (1).

         ┌─────────┬─────────┐   ┌─────────┬─────────┐         
 HEAD ──▶│  data   │"pointer"│──▶│  data   │"pointer"│──▶ null 
         └─────────┴────△────┘   └─────────┴─────────┘         
          SINGLE        │                                      
          REFERENCE ────┘                                                                

Código de ejemplo en C

Código de ejemplo en Java, con pruebas unitarias - lista enlazada individualmente con referencia de cabecera

Lista enlazada XOR

Una lista enlazada XOR también se conoce como una lista enlazada eficiente en memoria . Es otra forma de una lista doblemente enlazada. Esto depende en gran medida de la puerta lógica XOR y sus propiedades.

¿Por qué esto se llama la lista enlazada de memoria eficiente?

Esto se denomina así porque utiliza menos memoria que una lista tradicional doble vinculada.


¿Es esto diferente de una lista doblemente vinculada?

Si lo es

Una lista de enlace doble almacena dos punteros, que apuntan al siguiente nodo y al anterior. Básicamente, si desea volver, vaya a la dirección señalada con el puntero back . Si desea avanzar, vaya a la dirección señalada por el next puntero. Es como:

introduzca la descripción de la imagen aquí

Una lista enlazada eficiente en memoria , o la lista enlazada XOR solo tiene un puntero en lugar de dos. Esto almacena la dirección anterior (addr (anterior)) XOR (^) la siguiente dirección (addr (siguiente)). Cuando desee pasar al siguiente nodo, realice ciertos cálculos y busque la dirección del siguiente nodo. Esto es lo mismo para ir al nodo anterior. Es como:

introduzca la descripción de la imagen aquí


¿Como funciona?

Para entender cómo funciona la lista enlazada, necesita conocer las propiedades de XOR (^):

|-------------|------------|------------|
|    Name     |   Formula  |    Result  |
|-------------|------------|------------|
| Commutative |    A ^ B   |    B ^ A   |
|-------------|------------|------------|
| Associative | A ^ (B ^ C)| (A ^ B) ^ C|
|-------------|------------|------------|
| None (1)    |    A ^ 0   |     A      |
|-------------|------------|------------|
| None (2)    |    A ^ A   |     0      |
|-------------|------------|------------|
| None (3)    | (A ^ B) ^ A|     B      |
|-------------|------------|------------|

Ahora dejemos esto de lado y veamos qué almacena cada nodo.

El primer nodo, o la cabeza , almacena 0 ^ addr (next) ya que no hay ningún nodo o dirección anterior. Se parece a esto .

Luego, el segundo nodo almacena addr (prev) ^ addr (next) . Se parece a esto .

La cola de la lista, no tiene ningún nodo siguiente, por lo que almacena addr (prev) ^ 0 . Se parece a esto .

Pasando de la cabeza al siguiente nodo

Digamos que ahora estás en el primer nodo, o en el nodo A. Ahora quieres moverte en el nodo B. Esta es la fórmula para hacerlo:

Address of Next Node = Address of Previous Node ^ pointer in the current Node

Así que esto sería:

addr (next) = addr (prev) ^ (0 ^ addr (next))

Como esta es la cabecera, la dirección anterior simplemente sería 0, así que:

addr (next) = 0 ^ (0 ^ addr (next))

Podemos eliminar los paréntesis:

addr (next) = 0 ^ 0 addr (next)

Usando la propiedad none (2) , podemos decir que 0 ^ 0 siempre será 0:

addr (next) = 0 ^ addr (next)

Usando la propiedad none (1) , podemos simplificarla para:

addr (next) = addr (next)

Tienes la dirección del siguiente nodo!

Pasando de un nodo al siguiente nodo

Ahora digamos que estamos en un nodo medio, que tiene un nodo anterior y el siguiente.

Apliquemos la fórmula:

Address of Next Node = Address of Previous Node ^ pointer in the current Node

Ahora sustituye los valores:

addr (next) = addr (prev) ^ (addr (prev) ^ addr (next))

Eliminar paréntesis:

addr (next) = addr (prev) ^ addr (prev) ^ addr (next)

Usando la propiedad none (2) , podemos simplificar:

addr (next) = 0 ^ addr (next)

Usando la propiedad none (1) , podemos simplificar:

addr (next) = addr (next)

Y lo entiendes!

Pasando de un nodo al nodo en el que estaba anteriormente

Si no entiende el título, básicamente significa que si estuvo en el nodo X y ahora se ha movido al nodo Y, desea volver al nodo visitado anteriormente, o básicamente al nodo X.

Esto no es una tarea engorrosa. Recuerde que mencioné anteriormente, que almacena la dirección en la que se encontraba en una variable temporal. Por lo tanto, la dirección del nodo que desea visitar está en una variable:

addr (prev) = temp_addr

Mover de un nodo al nodo anterior

Esto no es lo mismo que se mencionó anteriormente. Quiero decir que estabas en el nodo Z, ahora estás en el nodo Y y quieres ir al nodo X.

Esto es casi lo mismo que pasar de un nodo al siguiente nodo. Solo que esto es viceversa. Cuando escriba un programa, utilizará los mismos pasos que mencioné al pasar de un nodo al siguiente, solo que está encontrando el elemento anterior en la lista que el elemento siguiente.


Código de ejemplo en C

/* C/C++ Implementation of Memory efficient Doubly Linked List */
#include <stdio.h>
#include <stdlib.h>
 
// Node structure of a memory efficient doubly linked list
struct node
{
    int data;
    struct node* npx;  /* XOR of next and previous node */
};
 
/* returns XORed value of the node addresses */
struct node* XOR (struct node *a, struct node *b)
{
    return (struct node*) ((unsigned int) (a) ^ (unsigned int) (b));
}
 
/* Insert a node at the begining of the XORed linked list and makes the
   newly inserted node as head */
void insert(struct node **head_ref, int data)
{
    // Allocate memory for new node
    struct node *new_node  = (struct node *) malloc (sizeof (struct node) );
    new_node->data = data;
 
    /* Since new node is being inserted at the begining, npx of new node
       will always be XOR of current head and NULL */
    new_node->npx = XOR(*head_ref, NULL);
 
    /* If linked list is not empty, then npx of current head node will be XOR 
       of new node and node next to current head */
    if (*head_ref != NULL)
    {
        // *(head_ref)->npx is XOR of NULL and next. So if we do XOR of 
        // it with NULL, we get next
        struct node* next = XOR((*head_ref)->npx,  NULL);
        (*head_ref)->npx = XOR(new_node, next);
    }
 
    // Change head
    *head_ref = new_node;
}
 
// prints contents of doubly linked list in forward direction
void printList (struct node *head)
{
    struct node *curr = head;
    struct node *prev = NULL;
    struct node *next;
 
    printf ("Following are the nodes of Linked List: \n");
 
    while (curr != NULL)
    {
        // print current node
        printf ("%d ", curr->data);
 
        // get address of next node: curr->npx is next^prev, so curr->npx^prev
        // will be next^prev^prev which is next
        next = XOR (prev, curr->npx);
 
        // update prev and curr for next iteration
        prev = curr;
        curr = next;
    }
}
 
// Driver program to test above functions
int main ()
{
    /* Create following Doubly Linked List
       head-->40<-->30<-->20<-->10   */
    struct node *head = NULL;
    insert(&head, 10);
    insert(&head, 20);
    insert(&head, 30);
    insert(&head, 40);
 
    // print the created list
    printList (head);
 
    return (0);
}

El código anterior realiza dos funciones básicas: inserción y transversal.

  • Inserción: Esto se realiza mediante la función insert . Esto inserta un nuevo nodo al principio. Cuando se llama a esta función, coloca el nuevo nodo como cabecera y el nodo anterior como segundo nodo.

  • Traversal: Esto se realiza mediante la función printList . Simplemente pasa por cada nodo e imprime el valor.

Tenga en cuenta que XOR de punteros no está definido por el estándar C / C ++. Por lo tanto, la implementación anterior puede no funcionar en todas las plataformas.


Referencias

Tenga en cuenta que he tomado un montón de contenido de mi propia respuesta en main.

Omitir lista

Las listas de omisiones son listas vinculadas que le permiten saltar al nodo correcto. Este es un método que es mucho más rápido que una lista enlazada individualmente normal. Básicamente es una lista enlazada individualmente, pero los punteros no van de un nodo al siguiente, sino que omiten algunos nodos. De ahí el nombre "Skip List".


¿Es esto diferente de una lista enlazada individualmente?

Si lo es

Una lista enlazada individualmente es una lista en la que cada nodo apunta al siguiente nodo. Una representación gráfica de una lista enlazada individualmente es como:

introduzca la descripción de la imagen aquí

Una lista de omisiones es una lista con cada nodo que apunta a un nodo que puede o no estar detrás de él. Una representación gráfica de una lista de omisión es:

introduzca la descripción de la imagen aquí


¿Como funciona?

La lista de saltos es simple. Digamos que queremos acceder al nodo 3 en la imagen de arriba. No podemos tomar la ruta de ir de la cabeza al cuarto nodo, ya que está después del tercer nodo. Entonces vamos de la cabeza al segundo nodo, y luego al tercero.

Una representación gráfica es como:

introduzca la descripción de la imagen aquí


Referencias

Lista doblemente vinculada

Las listas enlazadas doblemente son un tipo de lista enlazada . Los nodos de una lista doblemente enlazada tienen dos "punteros" a otros nodos, "siguiente" y "anterior". Se llama lista enlazada doble porque cada nodo solo tiene dos "punteros" a otros nodos. Una lista doblemente enlazada puede tener un puntero de cabeza y / o cola.

         ┌─────────┬─────────┬─────────┐   ┌─────────┬─────────┬─────────┐         
 null ◀──┤previous │  data   │  next   │◀──┤previous │  data   │  next   │         
         │"pointer"│         │"pointer"│──▶│"pointer"│         │"pointer"│──▶ null 
         └─────────┴─────────┴─────────┘   └─────────┴─────────┴─────────┘         
                          ▲                     △                   △              
                     HEAD │                     │     DOUBLE        │              
                                                └──── REFERENCE ────┘                 

Las listas con enlaces dobles son menos eficientes en espacio que las listas con enlaces individuales; sin embargo, para algunas operaciones, ofrecen mejoras significativas en la eficiencia de tiempo. Un ejemplo simple es la función get , que para una lista doblemente enlazada con una referencia de cabecera y cola comenzará desde la cabecera o la cola, dependiendo del índice del elemento que se obtenga. Para una lista de elementos n , para obtener el elemento indexado n/2 + i , una lista enlazada individualmente con referencias de cabecera / cola debe atravesar los nodos n/2 + i , porque no puede "retroceder" desde la cola. Una lista doblemente enlazada con referencias cabeza / cola solo tiene que atravesar n/2 - i nodos, ya que puede "retroceder" desde la cola, atravesando la lista en orden inverso.

Código de ejemplo en C

Un ejemplo básico de SinglyLinkedList en Java

Una implementación básica para la lista enlazada individualmente en java - que puede agregar valores enteros al final de la lista, eliminar el primer valor encontrado del valor, devolver una matriz de valores en un instante dado y determinar si un valor dado está presente en la lista.

Node.java

package com.example.so.ds;


/**
 * <p> Basic node implementation </p>
 * @since 20161008
 * @author Ravindra HV
 * @version 0.1
 */
public class Node {
    
    private Node next;
    private int value;
    
    public Node(int value) {
        this.value=value;
    }

    public Node getNext() {
        return next;
    }

    public void setNext(Node next) {
        this.next = next;
    }

    public int getValue() {
        return value;
    }
    
    
}

SinglyLinkedList.java

package com.example.so.ds;


/**
 * <p> Basic single-linked-list implementation </p>
 * @since 20161008
 * @author Ravindra HV
 * @version 0.2
 */
public class SinglyLinkedList {
    
    private Node head;
    private volatile int size;
    
    public int getSize() {
        return size;
    }
    
    public synchronized void append(int value) {
        
        Node entry = new Node(value);
        if(head == null) {
            head=entry;
        }
        else {
            Node temp=head;
            while( temp.getNext() != null) {
                temp=temp.getNext();
            }
            temp.setNext(entry);
        }
        
        size++;
    }
    
    
    public synchronized boolean removeFirst(int value) {
        boolean result = false;
        
        if( head == null ) { // or size is zero..
            // no action
        }
        else if( head.getValue() == value ) {
            head = head.getNext();
            result = true;
        }
        else {

            Node previous = head;
            Node temp = head.getNext();
            while( (temp != null) && (temp.getValue() != value) ) {
                previous = temp;
                temp = temp.getNext();
            }
            
            if((temp != null) && (temp.getValue() == value)) { // if temp is null then not found..
                previous.setNext( temp.getNext() );
                result = true;
            }

        }
        
        if(result) {
            size--;
        }
        
        return result;
        
    }
    
    public synchronized int[] snapshot() {
        Node temp=head;
        int[] result = new int[size];
        for(int i=0;i<size;i++) {
            result[i]=temp.getValue();
            temp = temp.getNext();
        }
        return result;
    }
    
    public synchronized boolean contains(int value) {
        boolean result = false;
        Node temp = head;
        
        while(temp!=null) {
            if(temp.getValue() == value) {
                result=true;
                break;
            }
            temp=temp.getNext();
        }
        return result;
    }
    
}

TestSinglyLinkedList.java

package com.example.so.ds;

import java.util.Arrays;
import java.util.Random;


/**
 * 
 * <p> Test-case for single-linked list</p>
 * @since 20161008
 * @author Ravindra HV
 * @version 0.2
 *
 */
public class TestSinglyLinkedList {

    /**
     * @param args
     */
    public static void main(String[] args) {
        
        SinglyLinkedList singleLinkedList = new SinglyLinkedList();
        
        int loop = 11;
        Random random = new Random();
        
        for(int i=0;i<loop;i++) {
            
            int value = random.nextInt(loop);
            singleLinkedList.append(value);

            System.out.println();
            System.out.println("Append :"+value);
            System.out.println(Arrays.toString(singleLinkedList.snapshot()));
            System.out.println(singleLinkedList.getSize());
            System.out.println();
        }
        
        for(int i=0;i<loop;i++) {
            int value = random.nextInt(loop);
            boolean contains = singleLinkedList.contains(value);
            singleLinkedList.removeFirst(value);
            
            System.out.println();
            System.out.println("Contains :"+contains);
            System.out.println("RemoveFirst :"+value);
            System.out.println(Arrays.toString(singleLinkedList.snapshot()));
            System.out.println(singleLinkedList.getSize());
            System.out.println();
        }
        

    }

}


Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow