Linked List Problems
Linked List Problems
Linked List Presents the greatest recursive pointer problem ever devised.
Why Linked Lists Are Great To Study
Linked lists hold a special place in the hearts of many programmers. Linked lists are great
to study because...
Problems
By Nick Parlante Copyright ©1998-2002, Nick Parlante
• Nice Domain The linked list structure itself is simple. Many linked list
operations such as "reverse a list" or "delete a list" are easy to describe and
understand since they build on the simple purpose and structure of the
linked list itself.
• Complex Algorithm Even though linked lists are simple, the algorithms
Abstract that operate on them can be as complex and beautiful as you want (See
This document reviews basic linked list code techniques and then works through 18 problem #18). It's easy to find linked list algorithms that are complex, and
linked list problems covering a wide range of difficulty. Most obviously, these problems pointer intensive.
are a way to learn about linked lists. More importantly, these problems are a way to
develop your ability with complex pointer algorithms. Even though modern languages • Pointer Intensive Linked list problems are really about pointers. The
and tools have made linked lists pretty unimportant for day-to-day programming, the linked list structure itself is obviously pointer intensive. Furthermore,
skills for complex pointer algorithms are very important, and linked lists are an excellent linked list algorithms often break and re-weave the pointers in a linked list
way to develop those skills. as they go. Linked lists really test your understanding of pointers.
The problems use the C language syntax, so they require a basic understanding of C and
its pointer syntax. The emphasis is on the important concepts of pointer manipulation and • Visualization Visualization is an important skill in programming and
linked list algorithms rather than the features of the C language. design. Ideally, a programmer can visualize the state of memory to help
think through the solution. Even the most abstract languages such as Java
For some of the problems we present multiple solutions, such as iteration vs. recursion, and Perl have layered, reference based data structures that require
dummy node vs. local reference. The specific problems are, in rough order of difficulty: visualization. Linked lists have a natural visual structure for practicing this
Count, GetNth, DeleteList, Pop, InsertNth, SortedInsert, InsertSort, Append, sort of thinking. It's easy to draw the state of a linked list and use that
FrontBackSplit, RemoveDuplicates, MoveNode, AlternatingSplit, ShuffleMerge, drawing to think through the code.
SortedMerge, SortedIntersect, Reverse, and RecursiveReverse.
Not to appeal to your mercenary side, but for all of the above reasons, linked list
Contents problems are often used as interview and exam questions. They are short to state, and
Section 1 — Review of basic linked list code techniques 3 have complex, pointer intensive solutions. No one really cares if you can build linked
Section 2 — 18 list problems in increasing order of difficulty 10 lists, but they do want to see if you have programming agility for complex algorithms and
Section 3 — Solutions to all the problems 20 pointer manipulation. Linked lists are the perfect source of such problems.
This is document #105, Linked List Problems, in the Stanford CS Education Library. How To Use This Document
This and other free educational materials are available at http://cslibrary.stanford.edu/. Try not to use these problems passively. Take some time to try to solveeach problem.
This document is free to be used, reproduced, or sold so long as this notice is clearly Even if you do not succeed, you will think through the right issues in the attempt, and
reproduced at its beginning. looking at the given solution will make more sense. Use drawings to think about the
problems and work through the solutions. Linked lists are well-suited for memory
Related CS Education Library Documents drawings, so these problems are an excellent opportunity to develop your visualization
Related Stanford CS Education library documents... skill. The problems in this document use regular linked lists, without simplifcations like
dummy headers.
• Linked List Basics (http://cslibrary.stanford.edu/103/)
Explains all the basic issues and techniques for building linked lists. Dedication
• Pointers and Memory (http://cslibrary.stanford.edu/102/) This Jan-2002 revision includes many small edits. The first major release was Jan 17,
Explains how pointers and memory work in C and other languages. Starts 1999. Thanks to Negar Shamma for her many corrections. This document is distributed
with the very basics, and extends through advanced topics such as for the benefit and education of all. Thanks to the support of Eric Roberts and Stanford
reference parameters and heap management. University. That someone seeking education should have the opportunity to find it. May
you learn from it in the spirit of goodwill in which it is given.
• Binary Trees (http://cslibrary.stanford.edu/110/) Best Regards, Nick Parlante -- [email protected]
Introduction to binary trees
• Essential C (http://cslibrary.stanford.edu/101/)
Explains the basic features of the C programming language.
3 4
Alternately, some people prefer to write the loop as a for which makes the initialization,
test, and pointer advance more centralized, and so harder to omit...
for (current = head; current != NULL; current = current->next) {
5 6
2. Changing a Pointer Using a Reference Pointer Many of the functions in this document use reference pointer parameters. See the use of
Many list functions need to change the caller's head pointer. In C++, you can just declare Push() above and its implementation in the appendix for another example of reference
the pointer parameter as an & argument, and the compiler takes care of the details. To do pointers. See problem #8 and its solution for a complete example with drawings. For
this in the C language, pass a pointer to the head pointer. Such a pointer to a pointer is more detailed explanations, see the resources listed on page 1.
sometimes called a "reference pointer". The main steps for this technique are...
3. Build — At Head With Push()
• Design the function to take a pointer to the head pointer. This is the The easiest way to build up a list is by adding nodes at its "head end" with Push(). The
standard technique in C — pass a pointer to the "value of interest" that code is short and it runs fast — lists naturally support operations at their head end. The
needs to be changed. To change a struct node*, pass a struct disadvantage is that the elements will appear in the list in the reverse order that they are
node**. added. If you don't care about order, then the head end is the best.
• Use '&' in the caller to compute and pass a pointer to the value of interest. struct node* AddAtHead() {
struct node* head = NULL;
• Use '*' on the parameter in the callee function to access and change the int i;
value of interest.
for (i=1; i<6; i++) {
Push(&head, i);
The following simple function sets a head pointer to NULL by using a reference }
parameter....
// Change the passed in head pointer to be NULL // head == {5, 4, 3, 2, 1};
// Uses a reference pointer to access the caller's memory return(head);
void ChangeToNull(struct node** headRef) { // Takes a pointer to }
// the value of interest
*headRef = NULL; // use '*' to access the value of interest 4. Build — With Tail Pointer
} What about adding nodes at the "tail end" of the list? Adding a node at the tail of a list
most often involves locating the last node in the list, and then changing its .next field
from NULL to point to the new node, such as the tail variable in the following
void ChangeCaller() { example of adding a "3" node to the end of the list {1, 2}...
struct node* head1;
struct node* head2;
tail 3
Stack
newNode
ChangeCaller()
head1 This is just a special case of the general rule: to insert or delete a node inside a list, you
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
ChangeToNull(&head1) 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...
headRef
7 8
5. Build — Special Case + Tail Pointer Some linked list implementations keep the dummy node as a permanent part of the list.
Consider the problem of building up the list {1, 2, 3, 4, 5} by appending the nodes to the For this "permanent dummy" strategy, the empty list is not represented by a NULL
tail end. The difficulty is that the very first node must be added at the head pointer, but all pointer. Instead, every list has a heap allocated dummy node at its head. Algorithms skip
the other nodes are inserted after the last node using a tail pointer. The simplest way to over the dummy node for all operations. That way the dummy node is always present to
deal with both cases is to just have two separate cases in the code. Special case code first provide the above sort of convenience in the code. I prefer the temporary strategy shown
adds the head node {1}. Then there is a separate loop that uses a tail pointer to add all the here, but it is a little peculiar since the temporary dummy node is allocated in the stack,
other nodes. The tail pointer is kept pointing at the last node, and each new node is added while all the other nodes are allocated in the heap. For production code, I do not use
at tail->next. The only "problem" with this solution is that writing separate special either type of dummy node. The code should just cope with the head node boundary
case code for the first node is a little unsatisfying. Nonetheless, this approach is a solid cases.
one for production code — it is simple and runs fast.
7. Build — Local References
struct node* BuildWithSpecialCase() { Finally, here is a tricky way to unify all the node cases without using a dummy node at
struct node* head = NULL; all. For this technique, we use a local "reference pointer" which always points to the last
struct node* tail; pointer in the list instead of to the last node. All additions to the list are made by
int i; following the reference pointer. The reference pointer starts off pointing to the head
pointer. Later, it points to the .next field inside the last node in the list. (A detailed
// Deal with the head node here, and set the tail pointer explanation follows.)
Push(&head, 1);
tail = head; struct node* BuildWithLocalRef() {
struct node* head = NULL;
// Do all the other nodes using 'tail' struct node** lastPtrRef= &head; // Start out pointing to the head pointer
for (i=2; i<6; i++) { int i;
Push(&(tail->next), i); // add node at tail->next
tail = tail->next; // advance tail to point to last node for (i=1; i<6; i++) {
} Push(lastPtrRef, i); // Add node at the last pointer in the list
lastPtrRef= &((*lastPtrRef)->next); // Advance to point to the
return(head); // head == {1, 2, 3, 4, 5}; // new last pointer
} }
1 — Count()
Write a Count() function that counts the number of times a given int occurs in a list. The
code for this has the classic list traversal structure as demonstrated in Length().
void CountTest() {
List myList = BuildOneTwoThree(); // build {1, 2, 3}
int count = Count(myList, 2); // returns 1 since there's 1 '2' in the list
}
/*
Given a list and an int, return the number of times that int occurs
in the list.
*/
int Count(struct node* head, int searchFor) {
// Your code
11 12
2 — GetNth()
Write a GetNth() function that takes a linked list and an integer index and returns the data Stack Heap
value stored in the node at that index position. GetNth() uses the C numbering convention
that the first node is index 0, the second is index 1, ... and so on. So for the list {42, 13, DeleteListTest()
666} GetNth() with index 1 should return 13. The index should be in the range [0..length-
1]. If it is not, GetNth() should assert() fail (or you could implement some other error myList
case strategy).
void GetNthTest() {
struct node* myList = BuildOneTwoThree(); // build {1, 2, 3}
int lastNode = GetNth(myList, 2); // returns the value 3
} 1 2 3
myList is
Essentially, GetNth() is similar to an array[i] operation — the client can ask for overwritten
elements by index number. However, GetNth() no a list is much slower than [ ] on an with the
array. The advantage of the linked list is its much more flexible memory management — value NULL.
we can Push() at any time to add more elements and the memory is allocated as needed.
// Given a list and an index, return the data The three heap blocks are deallocated by calls to
// in the nth node of the list. The nodes are numbered from 0. free(). Their memory will appear to be intact for
// Assert fails if the index is invalid (outside 0..lengh-1). a while, but the memory should not be
int GetNth(struct node* head, int index) { accessed.
// Your code
DeleteList()
The DeleteList() implementation will need to use a reference parameter just like Push()
3 — DeleteList() so that it can change the caller's memory (myList in the above sample). The
Write a function DeleteList() that takes a list, deallocates all of its memory and sets its implementation also needs to be careful not to access the .next field in each node after
head pointer to NULL (the empty list). the node has been deallocated.
void DeleteList(struct node** headRef) {
void DeleteListTest() {
// Your code
struct node* myList = BuildOneTwoThree(); // build {1, 2, 3}
}
DeleteList(&myList); // deletes the three nodes and sets myList to NULL 4 — Pop()
Write a Pop() function that is the inverse of Push(). Pop() takes a non-empty list, deletes
the head node, and returns the head node's data. If all you ever used were Push() and
Pop(), then our linked list would really look like a stack. However, we provide more
Post DeleteList() Memory Drawing general functions like GetNth() which what make our linked list more than just a stack.
The following drawing shows the state of memory after DeleteList() executes in the Pop() should assert() fail if there is not a node to pop. Here's some sample code which
above sample. Overwritten pointers are shown in gray and deallocated heap memory has calls Pop()....
an 'X' through it. Essentially DeleteList() just needs to call free() once for each node and
set the head pointer to NULL. void PopTest() {
struct node* head = BuildOneTwoThree(); // build {1, 2, 3}
int a = Pop(&head); // deletes "1" node and returns 1
int b = Pop(&head); // deletes "2" node and returns 2
int c = Pop(&head); // deletes "3" node and returns 3
int len = Length(head); // the list is now empty, so len == 0
}
Pop() Unlink
Pop() is a bit tricky. Pop() needs to unlink the front node from the list and deallocate it
with a call to free(). Pop() needs to use a reference parameter like Push() so that it can
change the caller's head pointer. A good first step to writing Pop() properly is making the
memory drawing for what Pop() should do. Below is a drawing showing a Pop() of the
first node of a list. The process is basically the reverse of the 3-Step-Link-In used by
Push() (would that be "Ni Knil Pets-3"?). The overwritten pointer value is shown in gray,
and the deallocated heap memory has a big 'X' drawn on it...
13 14
InsertNth() is complex — you will want to make some drawings to think about your
solution and afterwards, to check its correctness.
Stack Heap
/*
A more general version of Push().
PopTest() Given a list, an index 'n' in the range 0..length,
head and a data element, add a new node to the list so
that it has the given index.
*/
void InsertNth(struct node** headRef, int index, int data) {
// your code...
Pop()
/*
The opposite of Push(). Takes a non-empty list
7 — InsertSort()
and removes the front node, and returns the data Write an InsertSort() function which given a list, rearranges its nodes so they are sorted in
which was in that node. increasing order. It should use SortedInsert().
*/
int Pop(struct node** headRef) { // Given a list, change it to be in sorted order (using SortedInsert()).
// your code... void InsertSort(struct node** headRef) { // Your code
5 — InsertNth() 8 — Append()
A more difficult problem is to write a function InsertNth() which can insert a new node at Write an Append() function that takes two lists, 'a' and 'b', appends 'b' onto the end of 'a',
any index within a list. Push() is similar, but can only insert a node at the head end of the and then sets 'b' to NULL (since it is now trailing off the end of 'a'). Here is a drawing of
list (index 0). The caller may specify any index in the range [0..length], and the new node a sample call to Append(a, b) with the start state in gray and the end state in black. At the
should be inserted so as to be at that index. end of the call, the 'a' list is {1, 2, 3, 4}, and 'b' list is empty.
void InsertNthTest() {
struct node* head = NULL; // start with the empty list
Stack Heap
InsertNth(&head, 0, 13); // build {13)
InsertNth(&head, 1, 42); // build {13, 42}
InsertNth(&head, 1, 5); // build {13, 5, 42}
a 1 2
DeleteList(&head); // clean up after ourselves
} b
3 4
15 16
It turns out that both of the head pointers passed to Append(a, b) need to be reference 10 RemoveDuplicates()
parameters since they both may need to be changed. The second 'b' parameter is always
set to NULL. When is 'a' changed? That case occurs when the 'a' list starts out empty. In Write a RemoveDuplicates() function which takes a list sorted in increasing order and
that case, the 'a' head must be changed from NULL to point to the 'b' list. Before the call deletes any duplicate nodes from the list. Ideally, the list should only be traversed once.
'b' is {3, 4}. After the call, 'a' is {3, 4}. /*
Remove duplicates from a sorted list.
*/
void RemoveDuplicates(struct node* head) {
Stack Heap // Your code...
a 11 — MoveNode()
This is a variant on Push(). Instead of creating a new node and pushing it onto the given
b list, MoveNode() takes two lists, removes the front node from the second list and pushes
it onto the front of the first. This turns out to be a handy utility function to have for
several later problems. Both Push() and MoveNode() are designed around the feature that
list operations work most naturally at the head of the list. Here's a simple example of
3 4 what MoveNode() should do...
void MoveNodeTest() {
struct node* a = BuildOneTwoThree(); // the list {1, 2, 3}
struct node* b = BuildOneTwoThree();
MoveNode(&a, &b);
// Append 'b' onto the end of 'a', and then set 'b' to NULL. // a == {1, 1, 2, 3}
void Append(struct node** aRef, struct node** bRef) { // b == {2, 3}
// Your code... }
/*
9 — FrontBackSplit() Take the node from the front of the source, and move it to
the front of the dest.
Given a list, split it into two sublists — one for the front half, and one for the back half. If It is an error to call this with the source list empty.
the number of elements is odd, the extra element should go in the front list. So */
FrontBackSplit() on the list {2, 3, 5, 7, 11} should yield the two lists {2, 3, 5} and {7, void MoveNode(struct node** destRef, struct node** sourceRef) {
11}. Getting this right for all the cases is harder than it looks. You should check your // Your code
solution against a few cases (length = 2, length = 3, length=4) to make sure that the list
gets split correctly near the short-list boundary conditions. If it works right for length=4,
it probably works right for length=1000. You will probably need special case code to deal
with the (length <2) cases. 12 — AlternatingSplit()
Write a function AlternatingSplit() that takes one list and divides up its nodes to make
Hint. Probably the simplest strategy is to compute the length of the list, then use a for two smaller lists. The sublists should be made from alternating elements in the original
loop to hop over the right number of nodes to find the last node of the front half, and then list. So if the original list is {a, b, a, b, a}, then one sublist should be {a, a, a} and the
cut the list at that point. There is a trick technique that uses two pointers to traverse the other should be {b, b}. You may want to use MoveNode() as a helper. The elements in
list. A "slow" pointer advances one nodes at a time, while the "fast" pointer goes two the new lists may be in any order (for some implementations, it turns out to be convenient
nodes at a time. When the fast pointer reaches the end, the slow pointer will be about half if they are in the reverse order from the original list.)
way. For either strategy, care is required to split the list at the right point.
/*
/* Given the source list, split its nodes into two shorter lists.
Split the nodes of the given list into front and back halves, If we number the elements 0, 1, 2, ... then all the even elements
and return the two lists using the reference parameters. should go in the first list, and all the odd elements in the second.
If the length is odd, the extra node should go in the front list. The elements in the new lists may be in any order.
*/ */
void FrontBackSplit(struct node* source, void AlternatingSplit(struct node* source,
struct node** frontRef, struct node** backRef) { struct node** aRef, struct node** bRef) {
// Your code... // Your code
17 18
17 — Reverse()
14 — SortedMerge() Write an iterative Reverse() function that reverses a list by rearranging all the .next
Write a SortedMerge() function that takes two lists, each of which is sorted in increasing pointers and the head pointer. Ideally, Reverse() should only need to make one pass of the
order, and merges the two together into one list which is in increasing order. list. The iterative solution is moderately complex. It's not so difficult that it needs to be
SortedMerge() should return the new list. The new list should be made by splicing this late in the document, but it goes here so it can be next to #18 Recursive Reverse
together the nodes of the first two lists (use MoveNode()). Ideally, Merge() should only which is quite tricky. The efficient recursive solution is quite complex (see next
make one pass through each list. Merge() is tricky to get right — it may be solved problem). (A memory drawing and some hints for Reverse() are below.)
iteratively or recursively. There are many cases to deal with: either 'a' or 'b' may be
empty, during processing either 'a' or 'b' may run out first, and finally there's the problem void ReverseTest() {
struct node* head;
of starting the result list empty, and building it up while going through 'a' and 'b'.
/* head = BuildOneTwoThree();
Takes two lists sorted in increasing order, and Reverse(&head);
splices their nodes together to make one big // head now points to the list {3, 2, 1}
sorted list which is returned.
*/ DeleteList(&head); // clean up after ourselves
struct node* SortedMerge(struct node* a, struct node* b) { }
// your code...
1 2 3
19 20
the existing node instead of allocating a new node. You can use MoveNode() to do most
of the work, or hand code the pointer re-arrangement.
2 — GetNth() Solution
Combine standard list iteration with the additional problem of counting over to find the
right node. Off-by-one errors are common in this sort of code. Check it carefully against a
The Tree-List Recursion Problem simple case. If it's right for n=0, n=1, and n=2, it will probably be right for n=1000.
Once you are done with these problems, see the best and most complex list recursion
int GetNth(struct node* head, int index) {
problem of all time: The great Tree-List-Recursion problem at struct node* current = head;
http://cslibrary.stanford.edu/109/ int count = 0; // the index of the node we're currently looking at
3 — DeleteList() Solution void InsertNth(struct node** headRef, int index, int data) {
// position 0 is a special case...
Delete the whole list and set the head pointer to NULL. There is a slight complication if (index == 0) Push(headRef, data);
inside the loop, since we need extract the .next pointer before we delete the node, since else {
after the delete it will be technically unavailable. struct node* current = *headRef;
int i;
void DeleteList(struct node** headRef) {
struct node* current = *headRef; // deref headRef to get the real head for (i=0; i<index-1; i++) {
struct node* next; assert(current != NULL); // if this fails, index was too big
current = current->next;
while (current != NULL) { }
next = current->next; // note the next pointer
free(current); // delete the node assert(current != NULL); // tricky: you have to check one last time
current = next; // advance to the next node
} Push(&(current->next), data); // Tricky use of Push() --
// The pointer being pushed on is not
*headRef = NULL; // Again, deref headRef to affect the real head back // in the stack. But actually this works
// in the caller. // fine -- Push() works for any node pointer.
} }
}
4 — Pop() Solution
Extract the data from the head node, delete the node, advance the head pointer to point at 6 — SortedInsert() Solution
the next node in line. Uses a reference parameter since it changes the head pointer. The basic strategy is to iterate down the list looking for the place to insert the new node.
That could be the end of the list, or a point just before a node which is larger than the new
int Pop(struct node** headRef) {
struct node* head; node. The three solutions presented handle the "head end" case in different ways...
int result; // Uses special case code for the head end
void SortedInsert(struct node** headRef, struct node* newNode) {
head = *headRef; // Special case for the head end
assert(head != NULL); if (*headRef == NULL || (*headRef)->data >= newNode->data) {
newNode->next = *headRef;
result = head->data; // pull out the data before the node is deleted *headRef = newNode;
}
*headRef = head->next; // unlink the head node for the caller else {
// Note the * -- uses a reference-pointer // Locate the node before the point of insertion
// just like Push() and DeleteList(). struct node* current = *headRef;
while (current->next!=NULL && current->next->data<newNode->data) {
free(head); // free the head node current = current->next;
}
return(result); // don't forget to return the data from the link newNode->next = current->next;
} current->next = newNode;
}
}
5 — InsertNth() Solution
This code handles inserting at the very front as a special case. Otherwise, it works by // Dummy node strategy for the head end
running a current pointer to the node before where the new node should go. Uses a for void SortedInsert2(struct node** headRef, struct node* newNode) {
loop to march the pointer forward. The exact bounds of the loop (the use of < vs <=, n vs. struct node dummy;
n-1) are always tricky — the best approach is to get the general structure of the iteration struct node* current = &dummy;
correct first, and then make a careful drawing of a couple test cases to adjust the n vs. n-1 dummy.next = *headRef;
cases to be correct. (The so called "OBOB" — Off By One Boundary cases.) The OBOB
cases are always tricky and not that interesting. Write the correct basic structure and then while (current->next!=NULL && current->next->data<newNode->data) {
use a test case to get the OBOB cases correct. Once the insertion point has been current = current->next;
}
determined, this solution uses Push() to do the link in. Alternately, the 3-Step Link In
code could be pasted here directly. newNode->next = current->next;
current->next = newNode;
23 24
*headRef = dummy.next; current->next = *bRef; // hang the b list off the last node
} }
7 — InsertSort() Solution }
Append(&a, &b);
Start with an empty result list. Iterate through the source list and SortedInsert() each of its
nodes into the result list. Be careful to note the .next field in each node before moving As an example of how reference parameters work, note how reference parameters in
it into the result list. Append() point back to the head pointers in AppendTest()...
// Given a list, change it to be in sorted order (using SortedInsert()).
void InsertSort(struct node** headRef) {
struct node* result = NULL; // build the answer here Stack Heap
struct node* current = *headRef; // iterate over the original list
struct node* next;
AppendTest()
while (current!=NULL) { 1 2
a
next = current->next; // tricky - note the next pointer before we change it
SortedInsert(&result, current);
current = next; b
}
11 — MoveNode() Solution
// Uses the fast/slow pointer strategy The MoveNode() code is most similar to the code for Push(). It's short — just changing a
void FrontBackSplit2(struct node* source, couple pointers — but it's complex. Make a drawing.
struct node** frontRef, struct node** backRef) {
struct node* fast; void MoveNode(struct node** destRef, struct node** sourceRef) {
struct node* slow; struct node* newNode = *sourceRef; // the front source node
assert(newNode != NULL);
if (source==NULL || source->next==NULL) { // length < 2 cases
*frontRef = source; *sourceRef = newNode->next; // Advance the source pointer
*backRef = NULL;
} newNode->next = *destRef; // Link the old dest off the new node
else { *destRef = newNode; // Move dest to point to the new node
slow = source; }
fast = source->next;
27 28
while (1) {
if (a == NULL) { // if either list runs out, use the other list SortedMerge() Using Recursion
tail->next = b; Merge() is one of those nice recursive problems where the recursive solution code is
break; much cleaner than the iterative code. You probably wouldn't want to use the recursive
} version for production code however, because it will use stack space which is
else if (b == NULL) { proportional to the length of the lists.
tail->next = a;
break; struct node* SortedMerge3(struct node* a, struct node* b) {
} struct node* result = NULL;
31 32
// Pick either a or b, and recur // Once one or the other list runs out -- we're done
if (a->data <= b->data) { while (a!=NULL && b!=NULL) {
result = a; if (a->data == b->data) {
result->next = SortedMerge3(a->next, b); Push((&tail->next), a->data);
} tail = tail->next;
else { a = a->next;
result = b; b = b->next;
result->next = SortedMerge3(a, b->next); }
} else if (a->data < b->data) { // advance the smaller list
a = a->next;
return(result); }
} else {
b = b->next;
}
}
15 — MergeSort() Solution
The MergeSort strategy is: split into sublists, sort the sublists recursively, merge the two return(dummy.next);
sorted lists together to form the answer. }
16 — SortedIntersect() Solution
The strategy is to advance up both lists and build the result list as we go. When the 17 — Reverse() Solution
current point in both lists are the same, add a node to the result. Otherwise, advance This first solution uses the "Push" strategy with the pointer re-arrangement hand coded
whichever list is smaller. By exploiting the fact that both lists are sorted, we only traverse inside the loop. There's a slight trickyness in that it needs to save the value of the
each list once. To build up the result list, both the dummy node and local reference "current->next" pointer at the top of the loop since the body of the loop overwrites that
strategy solutions are shown... pointer.
// This solution uses the temporary dummy to build up the result list /*
struct node* SortedIntersect(struct node* a, struct node* b) { Iterative list reverse.
struct node dummy; Iterate through the list left-right.
33 34
struct node* front = middle->next; // the two other pointers (NULL ok)
struct node* back = NULL;
while (1) {
middle->next = back; // fix the middle node
35
Appendix
Basic Utility Function Implementations
Here is the source code for the basic utility functions.
Length()
// Return the number of nodes in a list
int Length(struct node* head) {
int count = 0;
struct node* current = head;
return(count);
}
Push()
// Given a reference (pointer to pointer) to the head
// of a list and an int, push a new node on the front of the list.
// Creates a new node with the int, links the list off the .next of the
// new node, and finally changes the head to point to the new node.
void Push(struct node** headRef, int newData) {
struct node* newNode =
(struct node*) malloc(sizeof(struct node)); // allocate node
newNode->data = newData; // put in the data
newNode->next = (*headRef); // link the old list off the new node
(*headRef) = newNode; // move the head to point to the new node
}
BuildOneTwoThree()
// Build and return the list {1, 2, 3}
struct node* BuildOneTwoThree() {
struct node* head = NULL; // Start with the empty list
Push(&head, 3); // Use Push() to add all the data
Push(&head, 2);
Push(&head, 1);
return(head);
}