This is an old revision of this page, as edited by Dcoetzee (talk | contribs) at 05:25, 11 March 2004 (Added big pseudocode section, removed advertising links). The present address (URL) is a permanent link to this revision, which may differ significantly from the current revision.
Revision as of 05:25, 11 March 2004 by Dcoetzee (talk | contribs) (Added big pseudocode section, removed advertising links)(diff) ← Previous revision | Latest revision (diff) | Newer revision → (diff)In computer science, a linked list is one of the simplest data structures used in computer programming. It consists of a number of nodes, each containing one element and one (or more) links to other nodes. Linked lists permit insertion and removal of nodes at any point in the list in O(1) time. Linked lists do not allow random access.
The simplest kind of linked list is a singly linked list, which has one link per node. This link points to the next node in the list, or to a null value if it is the last node. This kind of list allows sequential access to elements only in one direction (from front to back).
A singly linked list containing three integer values
A more sophisticated kind of linked list is a doubly linked list. Each node has two links, one to the previous node and one to the next. This allows sequential access to the list in both directions.
An example of a doubly linked list.
There are two common variations to the types mentioned above. First is a circularly linked list, where the first and last nodes are linked together. This can be done for both singly and doubly linked lists. To traverse a circular linked list, you begin at any node and follow the list in either direction until you return to the original node. The other variation is the addition of a sentinel node at the beginning of the list. This node represents before the first node and/or after the last node of the list. It also makes it possible to have an empty list.
Singly linked lists have one strong advantage over doubly-linked lists: each tail of the list, meaning all the nodes following a chosen one, themselves form a singly linked list. Thus this type of list, sometimes also called a cons-based list, can be seen as a recursive datatype. This enables, for example, two different lists to share a common tail, which in turn enables one to add to the front of the list while keeping a reference to both the new and the old versions, making this type of list a persistent data structure.
Linked List Operations
When manipulating linked lists in-place, care must be taken to not use values that you have invalidated in previous assignments. This makes algorithms for inserting or deleting linked list nodes somewhat subtle. This section gives pseudocode for adding or removing nodes from singly, doubly, and circularly linked lists in-place. Throughout we will use null to refer to an end-of-list marker or sentinel, which may be implemented in a number of ways.
Singly-linked lists
Our node data structure will have two fields:
- data -- The data being stored in the node
- next -- A reference to the next node
We also keep a variable head which always points to the first node in the list. Traversal of a singly-linked list is easy:
node := head while node not null <do something with node.value> node := node.next
The following code inserts a node after an existing node in a singly linked list. Inserting a node before an existing one cannot be done; instead, you have to locate it while keeping track of the previous node.
insertAfter(node, newNode) newNode.next := node.next node.next := newNode
Inserting at the beginning of the list requires a separate function. This requires updating head.
insertBeginning(newNode) newNode.next := head head := newNode
Similarly, we have functions for removing the node after a given node, and for removing a node from the beginning of the list. To find and remove a particular node, one must again keep track of the previous element.
removeAfter(node) nodeBeingRemoved := node.next node.next := node.next.next destroy nodeBeingRemoved removeBeginning(node) nodeBeingRemoved := head head := head.next destroy nodeBeingRemoved
Doubly-linked lists
With doubly-linked lists there are even more pointers to update, but also less information is needed, since we can use backwards pointers to observe preceding elements in the list. This enables new operations, and eliminates special-case functions. We will add a prev field to our nodes, pointing to the previous element, and a tail variable which always points to the last node in the list. No null values are strictly needed for doubly linked lists with head and tail, since comparison with head and tail can be used instead, but by placing them before the first node and after the last we help maintain locality of reference.
Iterating through a doubly linked list can be done in either direction. In fact, direction can change many times, if desired.
Forwards
node := head while node not null <do something with node.value> node := node.next
Backwards
node := tail while node not null <do something with node.value> node := node.prev
These symmetric functions add a node either after or before a given node:
insertAfter(node,newNode) newNode.prev := node newNode.next := node.next if node.next is null tail := newNode else node.next.prev := newNode node.next := newNode insertBefore(node,newNode) newNode.prev := node.prev newNode.next := node if node.prev is null head := newNode else node.prev.next := newNode node.prev := newNode
Removing a node is easier, only requiring care with head and tail:
remove(node) if node.prev is null head = node.next else node.prev.next = node.next if node.next is null tail := node.prev else node.next.prev = node.prev destroy node
One subtle consequence of this procedure is that deleting the last element of a list sets both head and tail to null.
A curious way of implementing doubly linked lists that uses half as much space for pointers is the xor linked list.
Circularly-linked lists
Circularly-linked lists can be either singly or doubly linked. For both, their circular versions, which link all the nodes into a continuous circle without using null, benefit from the ability to traverse the full list beginning at any given node. This often allows us to avoid storing head and tail, although if the list may be empty we need a special representation for the empty list, such as a head variable which points to some node in the list or is null if it's empty; we use such a head here.
This representation significantly simplifies adding and removing nodes into a non-empty list, but empty lists are a special case. Assuming that someNode is some node in the list, this code iterates through that list starting with someNode:
Forwards
node := someNode do <do something with node.value> node := node.next while node not someNode
Notice the postponing of the test to the end of the loop. This is important for the case where the list contains only the single node someNode.
This simple function inserts a node into a doubly-linked circularly linked list after a given element:
insertAfter(node, newNode) newNode.next := node.next newNode.prev := node node.next.prev := newNode node.next := newNode
Inserting an element in a possibly empty list requires a special function:
insertBeginning(node) if head is null node.prev := node node.next := node else insertAfter(head.prev, node) head := node
Finally, removing a node must deal with the case where the list empties:
remove(node) if node.next is node head := null else node.next.prev := node.prev node.prev.next := node.next destroy node
Language Support
Many programming languages, such as LISP and Scheme have singly linked lists built in. In other languages it is simple to create the data structure using references. For example, one might create a doubly linked list in C like this:
typedef struct Person { struct Person *prev; // previous person in list struct Person *next; // next person in list char name; int age; };
An example of code that uses this structure:
Person first; // first item in the linked list Person *p = &first;
p->prev = NULL; p->next = NULL;
while(!feof(stdin)) // there's still input { p->next = (Person *)malloc(sizeof(Person)); // first link p->next->prev = p; p = p->next; p->next = NULL; printf("Enter name and age:\n"); // Ask for input scanf("%s %d", &(p->name), &(p->age)); // Use it }
Linked Lists On The Web
Some linked list materials are available from the Stanford Computer Science department: