Buscar..


Observaciones

El lenguaje C no define una estructura de datos de lista enlazada. Si está utilizando C y necesita una lista enlazada, debe usar una lista enlazada de una biblioteca existente (como GLib) o escribir su propia interfaz de lista enlazada. Este tema muestra ejemplos de listas vinculadas y listas dobles vinculadas que pueden utilizarse como punto de partida para escribir sus propias listas vinculadas.

Lista enlazada individualmente

La lista contiene nodos que se componen de un enlace llamado siguiente.

Estructura de datos

struct singly_node
{
  struct singly_node * next;
};

Lista doblemente enlazada

La lista contiene nodos que se componen de dos enlaces llamados anterior y siguiente. Los enlaces normalmente hacen referencia a un nodo con la misma estructura.

Estructura de datos

struct doubly_node
{
  struct doubly_node * prev;
  struct doubly_node * next;
};

Topoliges

Lineal o abierto

introduzca la descripción de la imagen aquí

Circular o anillo

introduzca la descripción de la imagen aquí

Procedimientos

Enlazar

Unir dos nodos juntos. introduzca la descripción de la imagen aquí

void doubly_node_bind (struct doubly_node * prev, struct doubly_node * next)
{
  prev->next = next;
  next->prev = prev;
}

Hacer una lista enlazada circularmente

introduzca la descripción de la imagen aquí

void doubly_node_make_empty_circularly_list (struct doubly_node * head)
{
  doubly_node_bind (head, head);
}

Hacer una lista enlazada linealmente

introduzca la descripción de la imagen aquí

void doubly_node_make_empty_linear_list (struct doubly_node * head, struct doubly_node * tail)
{
  head->prev = NULL;
  tail->next = NULL;
  doubly_node_bind (head, tail);
}

Inserción

Supongamos que una lista vacía siempre contiene un nodo en lugar de NULL. Entonces, los procedimientos de inserción no tienen que tomar NULL en consideración.

void doubly_node_insert_between
(struct doubly_node * prev, struct doubly_node * next, struct doubly_node * insertion)
{
  doubly_node_bind (prev, insertion);
  doubly_node_bind (insertion, next);
}

void doubly_node_insert_before
(struct doubly_node * tail, struct doubly_node * insertion)
{
  doubly_node_insert_between (tail->prev, tail, insertion);
}

void doubly_node_insert_after
(struct doubly_node * head, struct doubly_node * insertion)
{
  doubly_node_insert_between (head, head->next, insertion);
}

Insertar un nodo al comienzo de una lista enlazada individualmente

El código a continuación solicitará números y continuará agregándolos al comienzo de una lista vinculada.

/* This program will demonstrate inserting a node at the beginning of a linked list */

#include <stdio.h>
#include <stdlib.h>

struct Node {
  int data;
  struct Node* next;
};


void insert_node (struct Node **head, int nodeValue);
void print_list (struct Node *head);

int main(int argc, char *argv[]) {
  struct Node* headNode;
  headNode = NULL; /* Initialize our first node pointer to be NULL. */
  size_t listSize, i;
  do {
    printf("How many numbers would you like to input?\n");
  } while(1 != scanf("%zu", &listSize));

  for (i = 0; i < listSize; i++) {
    int numToAdd;
    do {
      printf("Enter a number:\n");
    } while (1 != scanf("%d", &numToAdd));

    insert_node (&headNode, numToAdd);
    printf("Current list after your inserted node: \n");
    print_list(headNode);
  }

  return 0;
}

void print_list (struct Node *head) {
  struct node* currentNode = head;

  /* Iterate through each link. */
  while (currentNode != NULL) {
      printf("Value: %d\n", currentNode->data);
      currentNode = currentNode -> next;
  }
}

void insert_node (struct Node **head, int nodeValue) {
  struct Node *currentNode = malloc(sizeof *currentNode);
  currentNode->data = nodeValue;
  currentNode->next = (*head);

  *head = currentNode;
}

Explicación para la inserción de nodos

Para entender cómo agregamos nodos al principio, echemos un vistazo a los posibles escenarios:

  1. La lista está vacía, por lo que necesitamos agregar un nuevo nodo. En cuyo caso, nuestra memoria se ve así donde HEAD es un puntero al primer nodo:
| HEAD | --> NULL

La línea currentNode->next = *headNode; asignará el valor de currentNode->next a ser NULL desde que headNode originalmente comienza con un valor de NULL .

Ahora, queremos configurar nuestro puntero de nodo principal para que apunte a nuestro nodo actual.

  -----      -------------
 |HEAD | --> |CURRENTNODE| --> NULL /* The head node points to the current node */
  -----      -------------

Esto se hace con *headNode = currentNode;

  1. La lista ya está poblada; Necesitamos agregar un nuevo nodo al principio. En aras de la simplicidad, comencemos con 1 nodo:
 -----    -----------
 HEAD --> FIRST NODE --> NULL
 -----    -----------

Con currentNode->next = *headNode , la estructura de datos se ve así:

 ---------        -----    ---------------------
 currentNode -->  HEAD --> POINTER TO FIRST NODE --> NULL
 ---------        -----    ---------------------

Lo cual, obviamente, necesita ser modificado ya que *headNode debería apuntar a currentNode .

 ----    -----------    ---------------
 HEAD -> currentNode -->     NODE       -> NULL
 ----    -----------    ---------------

Esto se hace con *headNode = currentNode;

Insertando un nodo en la posición n.

Hasta ahora, hemos observado la inserción de un nodo al principio de una lista enlazada individualmente . Sin embargo, la mayoría de las veces también querrá poder insertar nodos en otros lugares. El código escrito a continuación muestra cómo es posible escribir una función insert() para insertar nodos en cualquier lugar de las listas vinculadas.

#include <stdio.h>
#include <stdlib.h>

struct Node {
  int data;
  struct Node* next;
};

struct Node* insert(struct Node* head, int value, size_t position);
void print_list (struct Node* head);

int main(int argc, char *argv[]) {
  struct Node *head = NULL; /* Initialize the list to be empty */

  /* Insert nodes at positions with values: */
  head = insert(head, 1, 0);
  head = insert(head, 100, 1);
  head = insert(head, 21, 2);
  head = insert(head, 2, 3);
  head = insert(head, 5, 4);
  head = insert(head, 42, 2);

  print_list(head);
  return 0;
}

struct Node* insert(struct Node* head, int value, size_t position) {
  size_t i = 0;
  struct Node *currentNode;

  /* Create our node */
  currentNode = malloc(sizeof *currentNode);
  /* Check for success of malloc() here! */

  /* Assign data */
  currentNode->data = value;

  /* Holds a pointer to the 'next' field that we have to link to the new node.
     By initializing it to &head we handle the case of insertion at the beginning. */
  struct Node **nextForPosition = &head;
  /* Iterate to get the 'next' field we are looking for.
     Note: Insert at the end if position is larger than current number of elements. */
  for (i = 0; i < position && *nextForPosition != NULL; i++) {
      /* nextForPosition is pointing to the 'next' field of the node.
         So *nextForPosition is a pointer to the next node.
         Update it with a pointer to the 'next' field of the next node. */
      nextForPosition = &(*nextForPosition)->next;
  }

  /* Here, we are taking the link to the next node (the one our newly inserted node should
  point to) by dereferencing nextForPosition, which points to the 'next' field of the node
  that is in the position we want to insert our node at.
  We assign this link to our next value. */
  currentNode->next = *nextForPosition;

  /* Now, we want to correct the link of the node before the position of our
  new node: it will be changed to be a pointer to our new node. */
  *nextForPosition = currentNode;

  return head;
}

void print_list (struct Node* head) {
  /* Go through the list of nodes and print out the data in each node */
  struct Node* i = head;
  while (i != NULL) {
    printf("%d\n", i->data);
    i = i->next;
  }
}

Invertir una lista enlazada

También puede realizar esta tarea de forma recursiva, pero en este ejemplo he elegido utilizar un enfoque iterativo. Esta tarea es útil si está insertando todos sus nodos al comienzo de una lista vinculada . Aquí hay un ejemplo:

#include <stdio.h>
#include <stdlib.h>

#define NUM_ITEMS 10

struct Node {
  int data;
  struct Node *next;
};

void insert_node(struct Node **headNode, int nodeValue, int position);
void print_list(struct Node *headNode);
void reverse_list(struct Node **headNode);


int main(void) {
  int i;
  struct Node *head = NULL;

  for(i = 1; i <= NUM_ITEMS; i++) {
    insert_node(&head, i, i);
  }
  print_list(head);

  printf("I will now reverse the linked list\n");
  reverse_list(&head);
  print_list(head);
  return 0;
}

void print_list(struct Node *headNode) {
  struct Node *iterator;

  for(iterator = headNode; iterator != NULL; iterator = iterator->next) {
    printf("Value: %d\n", iterator->data);
  }
}

void insert_node(struct Node **headNode, int nodeValue, int position) {
  int i;
  struct Node *currentNode = (struct Node *)malloc(sizeof(struct Node));
  struct Node *nodeBeforePosition = *headNode;

  currentNode->data = nodeValue;

  if(position == 1) {
    currentNode->next = *headNode;
    *headNode = currentNode;
    return;
  }

  for (i = 0; i < position - 2; i++) {
    nodeBeforePosition = nodeBeforePosition->next;
  }

  currentNode->next = nodeBeforePosition->next;
  nodeBeforePosition->next = currentNode;
}

void reverse_list(struct Node **headNode) {
  struct Node *iterator = *headNode;
  struct Node *previousNode = NULL;
  struct Node *nextNode = NULL;

  while (iterator != NULL) {
    nextNode = iterator->next;
    iterator->next = previousNode;
    previousNode = iterator;
    iterator = nextNode;
  }

  /* Iterator will be NULL by the end, so the last node will be stored in
  previousNode.  We will set the last node to be the headNode */
  *headNode = previousNode;
}

Explicación para el método de lista inversa

Comenzamos el Nodo previousNode como NULL , ya que sabemos en la primera iteración del bucle, si estamos buscando el nodo antes del primer nodo principal, será NULL . El primer nodo se convertirá en el último nodo de la lista, y la siguiente variable, naturalmente, debería ser NULL .

Básicamente, el concepto de revertir la lista enlazada aquí es que en realidad revertimos los enlaces en sí mismos. El siguiente miembro de cada nodo se convertirá en el nodo anterior, así:

Head -> 1 -> 2 -> 3 -> 4 -> 5

Donde cada número representa un nodo. Esta lista se convertiría en:

1 <- 2 <- 3 <- 4 <- 5 <- Head

Finalmente, la cabeza debe apuntar al quinto nodo en su lugar, y cada nodo debe apuntar al nodo anterior de este.

El nodo 1 debe apuntar a NULL ya que no había nada antes de él. El nodo 2 debe apuntar al nodo 1, el nodo 3 debe apuntar al nodo 2, etcétera.

Sin embargo, hay un pequeño problema con este método. Si rompemos el enlace al siguiente nodo y lo cambiamos al nodo anterior, no podremos pasar al siguiente nodo en la lista ya que el enlace a él desapareció.

La solución a este problema es simplemente almacenar el siguiente elemento en una variable ( nextNode ) antes de cambiar el enlace.

Una lista doblemente enlazada.

Un ejemplo de código que muestra cómo se pueden insertar los nodos en una lista con doble enlace, cómo se puede revertir fácilmente la lista y cómo se puede imprimir a la inversa.

#include <stdio.h>
#include <stdlib.h>

/* This data is not always stored in a structure, but it is sometimes for ease of use */
struct Node {
  /* Sometimes a key is also stored and used in the functions */
  int data;
  struct Node* next;
  struct Node* previous;
};

void insert_at_beginning(struct Node **pheadNode, int value);
void insert_at_end(struct Node **pheadNode, int value);

void print_list(struct Node *headNode);
void print_list_backwards(struct Node *headNode);

void free_list(struct Node *headNode);

int main(void) {
  /* Sometimes in a doubly linked list the last node is also stored */
  struct Node *head = NULL;

  printf("Insert a node at the beginning of the list.\n");
  insert_at_beginning(&head, 5);
  print_list(head);

  printf("Insert a node at the beginning, and then print the list backwards\n");
  insert_at_beginning(&head, 10);
  print_list_backwards(head);

  printf("Insert a node at the end, and then print the list forwards.\n");

  insert_at_end(&head, 15);
  print_list(head);

  free_list(head);

  return 0;
}

void print_list_backwards(struct Node *headNode) {
  if (NULL == headNode)
  {
    return;
  }
  /*
  Iterate through the list, and once we get to the end, iterate backwards to print 
  out the items in reverse order (this is done with the pointer to the previous node).
  This can be done even more easily if a pointer to the last node is stored.
  */
  struct Node *i = headNode;
  while (i->next != NULL) {
    i = i->next; /* Move to the end of the list */
  }

  while (i != NULL) {
    printf("Value: %d\n", i->data);
    i = i->previous;
  }
}

void print_list(struct Node *headNode) {
  /* Iterate through the list and print out the data member of each node */
  struct Node *i;
  for (i = headNode; i != NULL; i = i->next) {
    printf("Value: %d\n", i->data);
  }
}

void insert_at_beginning(struct Node **pheadNode, int value) {
  struct Node *currentNode;

  if (NULL == pheadNode)
  {
    return;
  }
  /* 
  This is done similarly to how we insert a node at the beginning of a singly linked
  list, instead we set the previous member of the structure as well
  */
  currentNode = malloc(sizeof *currentNode);

  currentNode->next = NULL;
  currentNode->previous = NULL;
  currentNode->data = value;

  if (*pheadNode == NULL) { /* The list is empty */
    *pheadNode = currentNode;
    return;
  }

  currentNode->next = *pheadNode;
  (*pheadNode)->previous = currentNode;
  *pheadNode = currentNode;
}

void insert_at_end(struct Node **pheadNode, int value) {
  struct Node *currentNode;

  if (NULL == pheadNode)
  {
    return;
  }

  /*
  This can, again be done easily by being able to have the previous element.  It 
  would also be even more useful to have a pointer to the last node, which is commonly
  used.
  */
  
  currentNode = malloc(sizeof *currentNode);
  struct Node *i = *pheadNode;

  currentNode->data = value;
  currentNode->next = NULL;
  currentNode->previous = NULL;

  if (*pheadNode == NULL) {
    *pheadNode = currentNode;
    return;
  }

  while (i->next != NULL) { /* Go to the end of the list */
    i = i->next;
  }

  i->next = currentNode;
  currentNode->previous = i;
}

void free_list(struct Node *node) {
  while (node != NULL) {
    struct Node *next = node->next;
    free(node);
    node = next;
  }
}

Tenga en cuenta que a veces, almacenar un puntero al último nodo es útil (es más eficiente simplemente poder saltar directamente al final de la lista que tener que recorrer hasta el final):

struct Node *lastNode = NULL;

En cuyo caso, es necesario actualizarlo tras los cambios en la lista.

A veces, una clave también se utiliza para identificar elementos. Es simplemente un miembro de la estructura del Nodo:

struct Node {
  int data;
  int key;
  struct Node* next;
  struct Node* previous;
};

La clave se utiliza cuando se realizan tareas en un elemento específico, como eliminar elementos.



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