need a pointer to the node just before that position, so you can change its .next field.
Many list problems include the sub-problem of advancing a pointer to the node before the
point of insertion or deletion. The one exception is if the operation falls on the first node
in the list — in that case the head pointer itself must be changed. The following examples
show the various ways code can handle the single head case and all the interior cases...
7
5. Build — Special Case + Tail Pointer
Consider the problem of building up the list {1, 2, 3, 4, 5} by appending the nodes to the
tail end. The difficulty is that the very first node must be added at the head pointer, but all
the other nodes are inserted after the last node using a tail pointer. The simplest way to
deal with both cases is to just have two separate cases in the code. Special case code first
adds the head node {1}. Then there is a separate loop that uses a tail pointer to add all the
other nodes. The tail pointer is kept pointing at the last node, and each new node is added
at tail->next. The only "problem" with this solution is that writing separate special
case code for the first node is a little unsatisfying. Nonetheless, this approach is a solid
one for production code — it is simple and runs fast.
struct node* BuildWithSpecialCase() {
struct node* head = NULL;
struct node* tail;
int i;
// Deal with the head node here, and set the tail pointer
Push(&head, 1);
tail = head;
// Do all the other nodes using 'tail'
for (i=2; i<6; i++) {
Push(&(tail->next), i); // add node at tail->next
tail = tail->next; // advance tail to point to last node
}
return(head); // head == {1, 2, 3, 4, 5};
}
6. Build — Temporary Dummy Node
This is a slightly unusual technique that can be used to shorten the code: Use a temporary
dummy node at the head of the list during the computation. The trick is that with the
dummy, every node appears to be added after the .next field of some other node. That
way the code for the first node is the same as for the other nodes. The tail pointer plays
the same role as in the previous example. The difference is that now it also handles the
first node as well.
struct node* BuildWithDummyNode() {
struct node dummy; // Dummy node is temporarily the first node
struct node* tail = &dummy; // Start the tail at the dummy.
// Build the list on dummy.next (aka tail->next)
int i;
dummy.next = NULL;
for (i=1; i<6; i++) {
Push(&(tail->next), i);
tail = tail->next;
}
// The real result list is now in dummy.next
// dummy.next == {1, 2, 3, 4, 5};
return(dummy.next);
}