Misplaced Pages

Linked list: Difference between revisions

Article snapshot taken from Wikipedia with creative commons attribution-sharealike license. Give it a read and then ask your questions in the chat. We can research this topic together.
Browse history interactively← Previous editNext edit →Content deleted Content addedVisualWikitext
Revision as of 01:37, 8 March 2004 editYacht (talk | contribs)2,894 editsm it this better?← Previous edit Revision as of 05:25, 11 March 2004 edit undoDcoetzee (talk | contribs)37,529 edits Added big pseudocode section, removed advertising linksNext edit →
Line 12: Line 12:


Singly linked lists have one strong advantage over doubly-linked lists: each <i>tail</i> 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 <i>cons-based list</i>, can be seen as a ]. 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 ]. Singly linked lists have one strong advantage over doubly-linked lists: each <i>tail</i> 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 <i>cons-based list</i>, can be seen as a ]. 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 ].

== 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 ], 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 ] 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:
<pre>
node := head
while node not null
<do something with node.value>
node := node.next
</pre>
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.
<pre>
insertAfter(node, newNode)
newNode.next := node.next
node.next := newNode
</pre>
Inserting at the beginning of the list requires a separate function. This requires updating ''head''.
<pre>
insertBeginning(newNode)
newNode.next := head
head := newNode
</pre>
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.
<pre>
removeAfter(node)
nodeBeingRemoved := node.next
node.next := node.next.next
destroy nodeBeingRemoved

removeBeginning(node)
nodeBeingRemoved := head
head := head.next
destroy nodeBeingRemoved
</pre>

=== 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 ].

Iterating through a doubly linked list can be done in either direction. In fact, direction can change many times, if desired.

''Forwards''
<pre>
node := head
while node not null
<do something with node.value>
node := node.next
</pre>
''Backwards''
<pre>
node := tail
while node not null
<do something with node.value>
node := node.prev
</pre>

These symmetric functions add a node either after or before a given node:
<pre>
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
</pre>
Removing a node is easier, only requiring care with ''head'' and ''tail'':
<pre>
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
</pre>
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 ].

=== 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''
<pre>
node := someNode
do
<do something with node.value>
node := node.next
while node not someNode
</pre>

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:

<pre>
insertAfter(node, newNode)
newNode.next := node.next
newNode.prev := node
node.next.prev := newNode
node.next := newNode
</pre>

Inserting an element in a possibly empty list requires a special function:
<pre>
insertBeginning(node)
if head is null
node.prev := node
node.next := node
else
insertAfter(head.prev, node)
head := node
</pre>

Finally, removing a node must deal with the case where the list empties:
<pre>
remove(node)
if node.next is node
head := null
else
node.next.prev := node.prev
node.prev.next := node.next
destroy node
</pre>


== Language Support == == Language Support ==
Line 40: Line 188:
scanf("%s %d", &(p->name), &(p->age)); // Use it scanf("%s %d", &(p->name), &(p->age)); // Use it
} }

A curious algorithm for linked lists is ]s.


== Linked Lists On The Web == == Linked Lists On The Web ==
Line 48: Line 194:
* *
* *

Some fundamental algorithm codes.
*
*


] ]

Revision as of 05:25, 11 March 2004

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: