Linear Lists

A linear list is one of the most fundamental and the most frequently used logical structures in data structures; structures such as stacks, queues and strings can all be seen as special cases or extensions of it. This chapter revolves around the central comparison of sequential storage versus linked storage: it introduces the definition of a linear list, the implementation and the basic operations of the sequential list and of the linked list, and it trains you to choose the right storage structure for a given scenario. After reading this chapter you will be able to understand the definition and the basic characteristics of a linear list, master the storage structure of the sequential list together with its insertion and deletion operations and the complexity analysis, master the storage structure of the singly linked list together with its insertion and deletion operations and the complexity analysis, learn the characteristics and the applicable scenarios of doubly linked lists, circular linked lists and static linked lists, and understand how to choose the right storage structure for the requirement at hand.

Questions This Chapter Answers

When a set of elements with a linear relationship has to be stored, do you choose a sequential list whose addresses are contiguous, or a linked list whose nodes are joined by pointers? The two have their own advantages and disadvantages in random access, in insertion and deletion, and in the use of the space. This chapter revolves around this central question of choosing a storage structure, and it introduces the implementation details of the two storage schemes, the differences in complexity and the scenarios in which each of them applies.

Basic Concepts

Basic Concepts

Linear List

A linear list (Linear List) is a finite sequence of n data elements of the same data type, written as L=(a1,a2,…,an)L=(a_1, a_2, \ldots, a_n).

  • Characteristic: there is a unique first element a1a_1 and a unique last element ana_n, and every element except the first and the last has exactly one predecessor and exactly one successor
  • The number of the elements n is called the length of the linear list; when n = 0 the list is called an empty list
Linear list

Click "Step forward" to watch the algorithm state change.

Comparison of the Storage Structures

Storage schemeContiguous addressesSupports random accessEfficiency of insertion and deletionSpace utilization
Sequential listYesYesLowLow
Linked listNoNoHighHigh
  • Sequential list: the elements of the linear list are stored one after another in a set of storage units with contiguous addresses, so logically adjacent elements are physically adjacent.
  • Linked list: every node contains a data field and a pointer field and the nodes are joined by pointers, so logically adjacent elements are not necessarily physically adjacent.

Exercises

Exercise 1

The characteristic of a linear list is ( )

A. The number of the elements is fixed and never changes B. Every element except the first and the last has exactly one predecessor and exactly one successor C. The storage addresses have to be contiguous D. All the elements may have different types

linear listpredecessor and successorlogical structure

Approach:

This question tests the logical characteristics of a linear list. When deciding it, distinguish the abstract logical structure of a linear list from a concrete storage structure such as a sequential list, and compare the four options one by one with respect to the number of the elements, the predecessors and the successors, the contiguity of the storage and the element types.

Steps:

  1. A linear list is a finite sequence of n (with n≥0n \ge 0) data elements of the same type, and its core characteristic is that every element except the first and the last has exactly one direct predecessor and exactly one direct successor.
  2. Option A, “the number of the elements is fixed and never changes”, is wrong because the length of a linear list can change; option C, “the storage addresses have to be contiguous”, is a characteristic of the sequential list and not a necessary characteristic of a linear list.
  3. Option D, “all the elements may have different types”, is wrong because a linear list requires all of its elements to have the same type.
  4. Only option B matches the definition of a linear list exactly.

Answer:

B.

Exercise 2

Briefly describe the definition and the main characteristics of a linear list.

linear listordered sequencelogical structure

Approach:

This question tests the definition of a linear list. In the answer, break the definition down into three levels, the number and the type of the elements, the unique first and last element, and the predecessor and successor relations, and then point out that this one-to-one relation is exactly where the word “linear” comes from.

Steps:

  1. Regarding the number and the type of the elements: a linear list is a finite sequence of n (with n≥0n \ge 0) data elements of the same type.
  2. Regarding the two ends: the list has a unique first element and a unique last element; when n=0n = 0 it is an empty list.
  3. Regarding the relations: every element except the first and the last has exactly one predecessor and exactly one successor, so the elements stand in a one-to-one linear relation.

Answer:

A linear list is an ordered sequence of n (with n >= 0) data elements of the same type; it has a unique first element and a unique last element, every element except the first and the last has exactly one predecessor and exactly one successor, and the elements stand in a one-to-one linear relation.

Exercise 3

What are the advantages and the disadvantages of the sequential list and of the linked list?

sequential listlinked liststorage structuretime complexity

Approach:

This question tests the comparison of the advantages and the disadvantages of the sequential list and of the linked list. In the answer, evaluate the two storage structures along three lines, the capability of random access, the cost of insertion and deletion, and the way the space is used, and then summarize the scenario in which each of them applies.

Steps:

  1. The advantages of the sequential list: the storage space is contiguous, it supports random access, searching is fast (O(1)O(1)) and the storage density is high; the disadvantages: an insertion or a deletion has to move a great many elements (O(n)O(n)), and the space has to be allocated in advance, so the space utilization is low.
  2. The advantages of the linked list: an insertion or a deletion only modifies the pointers (O(1)O(1) once the position has been located), the space is allocated on demand and the utilization is high; the disadvantages: random access is not supported, searching is slow (O(n)O(n)), every node needs an extra pointer and the storage density is low.
  3. Comparative conclusion: choose the sequential list when a frequent random access by index is needed and the size of the structure is relatively stable; choose the linked list when the elements are frequently inserted and deleted at a known position.

Answer:

Sequential list: the advantages are the support of random access, the fast search (O(1)O(1)) and the high storage density; the disadvantages are that an insertion or a deletion has to move a great many elements (O(n)O(n)) and the low space utilization, since the space has to be allocated in advance. Linked list: the advantages are the fast insertion and deletion (O(1)O(1) once the position has been located), the space allocated on demand and the high utilization; the disadvantages are that random access is not supported, that the search is slow (O(n)O(n)), that every node stores an extra pointer and that the storage density is low.

Sequential Storage

A sequential list is implemented with an array; it is the simplest and the most commonly used storage scheme. Its core operations are insertion, deletion and searching.

Insertion into a Sequential List

To insert the element x at position i, the elements from the i-th to the last one have to be shifted one place to the right, one after another.

#define MaxSize 100
typedef struct {
    int data[MaxSize];
    int length;  // current length
} SqList;

// insert the element x at position i (i starts from 1)
bool ListInsert(SqList &L, int i, int x) {
    if (i < 1 || i > L.length + 1) return false;  // illegal position
    if (L.length >= MaxSize) return false;        // the list is full
    for (int j = L.length; j >= i; j--)
        L.data[j] = L.data[j - 1];  // shift the elements to the right
    L.data[i - 1] = x;
    L.length++;
    return true;
}

Deletion from a Sequential List

To delete the element at position i, the elements from the (i+1)-th to the last one have to be shifted one place to the left, one after another.

// delete the element at position i and return it in e
bool ListDelete(SqList &L, int i, int &e) {
    if (i < 1 || i > L.length) return false;
    e = L.data[i - 1];
    for (int j = i; j < L.length; j++)
        L.data[j - 1] = L.data[j];  // shift the elements to the left
    L.length--;
    return true;
}

Complexity Derivation

Insertion: in the best case, inserting at the tail of the list, 0 elements are moved; in the worst case, inserting at the head of the list, n elements are moved; on average n2\frac{n}{2} elements are moved. Therefore:

Tinsert(n)=O(n)T_{insert}(n) = O(n)

Deletion: similarly, n−12\frac{n-1}{2} elements are moved on average, so Tdelete(n)=O(n)T_{delete}(n) = O(n).

Access by position: the element is reached directly through its index, so Tget(n)=O(1)T_{get}(n) = O(1).

Exercises

Exercise 1

In a sequential list of length n, inserting one element at position i (where 1≤i≤n+11 \le i \le n+1) requires moving how many elements ( )

A. n-i+1 B. n-i C. i D. i-1

sequential listinsertion operationelement shiftingtime complexity

Approach:

This question tests the number of the elements moved when an element is inserted into a sequential list by position. A sequential list relies on the contiguous storage to keep logically adjacent elements adjacent, and an insertion shifts all the elements behind the insertion point to the right, so the number of the moved elements is exactly the number of the elements from the insertion position to the tail of the list.

Steps:

  1. When an element is inserted at position ii, the ii-th element and every element behind it have to be moved one place to the right so that the relative order of the original elements is preserved.
  2. The elements that have to be moved are the ii-th to the nn-th, that is, n−i+1n - i + 1 elements in total.
  3. When i=n+1i = n + 1, that is, when the element is inserted at the tail of the list, the number of the moved elements is 0, which agrees with the formula.

Answer:

A (n - i + 1).

Exercise 2

How is the traversal of a linear list implemented?

sequential listlinked listtraversal operationpointer operation

Approach:

This question tests the way a sequential list and a linked list are traversed. Both of them have to visit every element in the logical order; the difference lies in how the next element is located, since a sequential list increments the index while a linked list advances the pointer.

Steps:

  1. Sequential list: the elements are stored in a contiguous space, and the loop for (i = 0; i < L.length; i++) visits L.data[i] one after another by index.
  2. Linked list: starting from the first node, the pointer p is made to point at the current node, the node is visited, and then p = p->next is executed until p is null.
  3. Both of them visit the elements in the same logical order and differ only in the way the elements are addressed, so the complexity of the traversal is O(n)O(n) for both of them.

Answer:

Sequential list: use a loop over the index, for (i = 0; i < L.length; i++) visits L.data[i] one after another. Linked list: starting from the first node, the pointer p visits the nodes one by one, p = p->next, until p is null.

Linked Storage

A singly linked list joins its nodes with pointers: an insertion and a deletion do not have to move any element, but a search has to start from the head of the list and traverse it.

typedef struct LNode {
    int data;
    struct LNode *next;
} LNode, *LinkList;

Insertion into a Singly Linked List

To insert a new node s after the i-th node, first let s point to the successor and then let the predecessor point to s. The order must not be reversed.

// insert the node s at position i (the insertion is made before the node at position i)
bool ListInsert(LinkList &L, int i, int x) {
    LNode *p = L;  // head node
    int j = 0;
    while (p != NULL && j < i - 1) {  // find the (i-1)-th node
        p = p->next;
        j++;
    }
    if (p == NULL) return false;
    LNode *s = (LNode *)malloc(sizeof(LNode));
    s->data = x;
    s->next = p->next;  // (1) the new node points to the successor
    p->next = s;        // (2) the predecessor points to the new node
    return true;
}

Deletion from a Singly Linked List

To delete the i-th node, it is enough to let the predecessor node point to the successor of the deleted node.

bool ListDelete(LinkList &L, int i, int &e) {
    LNode *p = L;
    int j = 0;
    while (p->next != NULL && j < i - 1) {  // find the (i-1)-th node
        p = p->next;
        j++;
    }
    if (p->next == NULL) return false;  // the i-th node does not exist
    LNode *q = p->next;
    e = q->data;
    p->next = q->next;  // skip the deleted node
    free(q);
    return true;
}

Complexity Derivation

  • Insertion and deletion: locating the position takes O(n)O(n), but modifying the pointers after the position has been located takes only O(1)O(1). If the predecessor of the insertion position is already known, the insertion and the deletion are O(1)O(1).
  • Search by value: the elements have to be compared one by one starting from the head of the list, so the worst case is O(n)O(n).
  • Access by position: the list has to be traversed, so the worst case is O(n)O(n).

Exercises

Exercise 1

The advantage of a singly linked list over a sequential list is ( )

A. It supports random access B. Its insertion and deletion operations are efficient C. Its storage density is higher D. Its search is more efficient

linked listinsertion and deletionrandom accessstorage density

Approach:

This question tests the advantages and the disadvantages of a singly linked list relative to a sequential list. In the answer, hold on to the essential property of a linked list, that the nodes are scattered in the memory and joined by pointers, and judge whether each of the four options holds.

Steps:

  1. An insertion and a deletion in a linked list only modify the pointer fields of the relevant nodes and do not move the elements as a whole as a sequential list does, so the insertion and the deletion are efficient and option B is correct.
  2. Option A is wrong: the nodes of a linked list are not stored contiguously, so the address of an element cannot be computed directly from its index and random access is not supported.
  3. Option C is wrong: every node of a linked list stores an extra pointer field, so its storage density is in fact lower than that of a sequential list.
  4. Option D is wrong: searching for a given element has to traverse the list node by node from the head node along the pointers, so the time complexity is O(n)O(n).

Answer:

B.

Exercise 2

What is the basic idea of the insertion and of the deletion in a singly linked list?

singly linked listinsertion operationdeletion operationpointer modification

Approach:

This question tests the basic idea of the insertion and of the deletion in a singly linked list. Both of them take finding the predecessor node as the premise, and they differ in whether the new node is first joined to the successor and the predecessor is then updated, or the predecessor simply steps over the deleted node.

Steps:

  1. Insertion: create the new node s, point s->next at the successor of the insertion position first, and then point the next field of the predecessor node at s, that is, connect first and break later.
  2. Deletion: find the predecessor p of the deleted node q, make p->next point to the successor of q, skip q and then release the memory of q.
  3. Both of them modify only a constant number of pointer fields and never move the other element nodes, so once the predecessor has been located the time complexity of both of them is O(1)O(1).

Answer:

Insertion: create the new node s, point s->next at the successor of the insertion position first, and then point the next field of the predecessor node at s, that is, connect first and break later. Deletion: find the predecessor p of the deleted node q, make p->next point to the successor of q, skip q and then release the memory of q.

Other Linked Lists

Doubly Linked List

Every node has two pointer fields, one pointing to the predecessor and one to the successor, so a bidirectional traversal is supported. The price is one extra pointer per node and a larger space cost.

typedef struct DNode {
    int data;
    struct DNode *prior, *next;
} DNode, *DLinkList;

The insertion operation (inserting s after the node p):

s->prior = p;
s->next = p->next;
p->next->prior = s;
p->next = s;

Circular Linked List

  • Circular singly linked list: the pointer of the last node points to the head node or to the first node, so that the whole list is connected end to end and every node can be reached starting from any node.
  • Circular doubly linked list: the prior of the head node points to the tail node, and the next of the tail node points to the head node.

A circular linked list is often used in the scenarios that operate frequently on the two ends, the Josephus ring and the process scheduling of an operating system for instance.

Static Linked List

A static linked list simulates a linked list with an array, and the “pointer” of a node is replaced by an array index, that is, a cursor; it suits the high-level languages that do not support pointers and the scenarios where the memory is limited.

Exercises

Exercise 1

Briefly describe the difference between a singly linked list and a doubly linked list and the scenario in which each of them applies.

singly linked listdoubly linked listpredecessor pointerapplication scenario

Approach:

This question tests the difference in the node structure between a singly linked list and a doubly linked list together with the trade-off between them. In the answer, start from the structural difference, namely the number of the pointer fields, and then give the typical application scenario of each of them.

Steps:

  1. Singly linked list: every node has only one pointer, which points to the successor; the structure is simple and it occupies little space, so it suits the scenarios in which only a traversal in one direction is needed.
  2. Doubly linked list: every node has two pointers, one to the predecessor and one to the successor, so a bidirectional traversal is supported, but every node occupies one more pointer of space.
  3. Choice of the scenario: choose the doubly linked list when the position has to be moved frequently in both directions, the cursor movement in a text editor and the LRU cache being the typical cases.

Answer:

Singly linked list: every node has only one pointer to the successor, the structure is simple and it occupies little space, so it suits the scenarios in which only a traversal in one direction is needed. Doubly linked list: every node has two pointers, one to the predecessor and one to the successor, so a bidirectional traversal is supported, but every node occupies one more pointer of space. It suits the scenarios in which the position has to be moved frequently in both directions, such as the cursor movement in a text editor and the LRU cache.

Typical Applications and Summary

The two preceding sections have made clear the difference in the storage between the sequential list and the linked list; this section puts them back into real decisions: task queues, browsing history, shopping carts and the undo and the redo of an editor are all built on a linear list underneath, and it is only the question of whether a random access is frequent that decides between the sequential list and the linked list. The common pitfalls at the end remind you that in a singly linked list the order of connecting first and breaking later must not be reversed during an insertion, and that is the line of code which is the easiest to get wrong.

Typical Applications

  • As the foundation of the other data structures: the linked storage of stacks, queues, strings and sparse matrices is in essence a linked list node with a different linking rule.
  • Task queues, browsing history and shopping carts: in these scenarios the elements are frequently added and removed at the two ends and are seldom accessed by their serial number in the middle, so a linked list, or a queue and a stack built on a linked list, avoids the cost of moving the elements that a sequential list would incur.
  • The undo and the redo of a text editor: every edit produces one record, and organizing the records with a stack naturally supports undoing the most recent operation and restoring the operation that has been undone.
Application scenarioRecommended structureReason
A frequent random access is neededSequential listThe address is obtained from the index with one multiplication and one addition, so the access is O(1)O(1)
Frequent insertions and deletions are neededLinked listOnce the predecessor is known the insertion and the deletion are O(1)O(1), and the following elements do not have to be moved
The operations are confined to the two endsDoubly ended linked listBoth ends support an insertion and a deletion in O(1)O(1)
The number of the elements changes frequently and a contiguous storage is requiredSequential listA linked list has the extra overhead of the nodes and a poor cache locality

Terminology Summary

TermAbbreviationExplanation
Sequential list-The elements are stored in a contiguous space and the address is computed from the index
Linked list-A node consists of a data field and a pointer field, and the nodes are chained by pointers
First node-The first node of the list; the pointer field points to it
Last node-The last node of the list; its pointer field is null
Head pointer-The pointer to the first node, the entry point of the list
Head node-The node attached in front of the first node, which makes it easy to handle the empty list and the insertion and the deletion in a uniform way
Length-The number of the nodes in the list

Common Pitfalls

Two other frequent mistakes come next. The first is mixing a list with a head node and a list without one: with a head node p points at the head node, and the first element that is inserted has to be inserted after the head node; without a head node the special case of the empty list has to be taken into account. The second is the condition for testing whether the list is empty: with a head node the empty list is head->next == NULL, and without a head node it is head == NULL.

Found an issue with this content?

Tell us what could be improved.