Linked Lists
Linked lists contain an unbounded number of elements linked together by explicit pointers. In this section, we deal with linear linked lists that represent sequences of elements. New elements can be inserted at the beginning or at the end of the list in $O(1)$.
Structure
In a simple linear linked list, each element is represented by a node that stores a value and a pointer to the next node. To represent the list, we use an empty head node instead of a pointer to the first element, as it simplifies the computation.
flowchart LR
    subgraph list
        direction LR
        head --> first[element 1] --> e[...] --> last[element n]
    end
We initialize the list by setting the head pointer to NULL. It signals that
the list is empty. We also keep a pointer to the last element, and when the list
is empty, it points to the head.
typedef struct node_s {
    int value;
    struct node_s *next;
} node_t;
typedef struct {
    node_t head;
    node_t *last;
} list_t;
void init(list_t *list) {
    list->head.next = NULL;
    list->last = &list->head;
}
int is_empty(list_t *list) {
    return list->head.next == NULL;
}
Search
Search for an element in the linked list.
Input A simple linked list with $n$ elements, and a value $x$ 
Output The node that contains $x$, or NULL if it is not present 
Time $O(n)$
In order to reuse this code later, we implement a function get_prior that
returns a prior node pointing to the one we seek. This function is guaranteed to
return a value different than NULL, since we use a head node.
flowchart LR
    subgraph list
        direction LR
        head --> e1[...] --> before --> x[element x] --> e2[...]
    end
    p([prior]) --> before
With a small change in the search condition, we can implement a sorted linked
list. If the element $x$ is not found, get_prior returns the node that should
come before $x$.
/* Unsorted list */
node_t * get_prior(list_t *list, int value) {
    node_t *node = &list->head;
    while (node->next != NULL && node->next->value != value) {
        node = node->next;
    }
    return node;
}
/* Sorted list */
node_t * get_prior(list_t *list, int value) {
    node_t *node = &list->head;
    while (node->next != NULL && node->next->value < value) {
        node = node->next;
    }
    return node;
}
node_t * search(list_t *list, int value) {
    node_t *prior = get_prior(list, value);
    return prior->next != NULL && prior->next->value == value
            ? prior->next : NULL;
}
Insertion
Insert an element in the linked list.
Input A simple linked list with $n$ elements, and a new value $x$ to be
inserted 
Effect Value $x$ is inserted in the list 
Time $O(1)$
In an unsorted list, insertion may be performed right after the head node or after the last node. In a sorted list, we must search the correct point to perform the insertion, and thus it runs in $O(n)$.
flowchart LR
    subgraph list
        direction LR
        head --> e1[...] --> before .-> after --> e2[...]
    end
    p([prior]) --> before
    n([new]) --> x[element x]
    before --> x --> after
#include <stdlib.h>
node_t * insert_after(node_t *prior, int value) {
    node_t *node = (node_t *) malloc(sizeof (node_t));
    node->value = value;
    node->next = prior->next;
    prior->next = node;
    return node;
}
void insert_at_start(list_t *list, int value) {
    node_t *node = insert_after(&list->head, value);
    if (list->last == &list->head) list->last = node;
}
void insert_at_end(list_t *list, int value) {
    list->last = insert_after(list->last, value);
}
/* Sorted list */
void insert(list_t *list, int value) {
    node_t *prior = get_prior(list, value);
    node_t *node = insert_after(prior, value);
    if (list->last == prior) list->last = node;
}
Removal
Remove an element from the linked list.
Input A simple linked list with $n$ elements, and a value $x$ to be
removed 
Effect Value $x$ is removed from the list, if it was present 
Time $O(n)$
If the element to be removed is always the first in the list, it can be done in $O(1)$. That is the case if the list represents a stack or a queue. To remove the last element efficiently, we should use a double-linked list instead of a simple linked list. All elements can be removed in $O(n)$.
flowchart LR
    subgraph list
        head --> e1[...] --> before .-> x[element x] .-> after --> e2[...]
        before --> after
    end
    p([prior]) --> before
    r([removed]) --> x
#include <stdlib.h>
int * remove_after(node_t *prior) {
    int *value = NULL;
    if (prior->next != NULL) {
        node_t *next = prior->next->next;
        value = prior->next->value;
        free(prior->next);
        prior->next = next;
    }
    return value;
}
void remove(list_t *list, int value) {
    node_t *prior = get_prior(list, key);
    if (prior->next != NULL && prior->next->value == value) {
        remove_after(prior);
        if (prior->next == NULL) list->last = prior;
    }
}
int * remove_first(list_t *list) {
    int *value = remove_after(&list->head);
    if (list->head.next == NULL) list->last = &list->head;
    return value;
}
void remove_all(list_t *list) {
    while (list->head.next != NULL) {
        node_t *node = list->head.next;
        list->head.next = node->next;
        free(node);
    }
    list->last = &list->head;
}
| Data structures | 
|---|
| Vectors | Linked Lists | Stacks | Queues | Hash Tables | Heaps | B-trees | Segment Tree | Union-Find Disjoint Set |