You are on page 1of 74

Chapter XL

Linked Lists II

Chapter LX Topics
40.1 Introduction

40.2 Dereferencing the NULL Pointer

40.3 Stack Library Functions

40.4 Queue Library Functions

40.5 Deallocating Memory Dynamically

40.6 The MyStack Class

40.7 The MyQueue Class

40.8 Doubly Linked Lists

40.9 Circular Linked Lists

40.10 Linked Lists of Linked Lists

40.11 Practice Exercises

Chapter XL Linked Lists II 40.1


40.1 Introduction

Linked lists are not neatly divided into two separate topics. There is only one
reason for creating two chapters on this important topic: volume. The large
volume of information associated with linked lists is easier to digest with two
chapters. Two chapters also allows a separation of quizzes, lab assignments and
especially two chapter tests for each part. The feedback of a test in the middle of
linked list materials is important for both teachers and students.

The previous chapter introduced the idea of linking memory locations that had
been allocated dynamically. This type of linking during program execution
promoted not only better space efficiency, but execution efficiency as well. You
learned how there are three fundamental linking approaches with a linked list.
Linking can be done in a LIFO sequence, a FIFO sequence or it can be ordered to
some desired linking sequence.

In this chapter we start by looking at some of the concepts learned in the previous
chapter and add practicality by a developing a library of functions to assist with
linking as a stack or as a queue. We will also show how to declare classes that
process linked data structure.

The chapter will finish by showing that there exist other linking approaches as
well and you will learn about linked lists that provide links in two directions,
linked lists that are circular and complex linked data structures that are linked lists
of linked lists.

40.2 Dereferencing the NULL Pointer

Dereferencing the NULL pointer is a big deal or rather avoiding dereferencing


the NULL pointer is really the big deal. In this section we want to make sure that
you understand and recognize this problem, and know the steps required to avoid
the evils of improper dereferencing. Now dereferencing is not necessarily bad.
In fact, you do it all the time with dynamic memory allocation programming. If
P is a pointer then it will reference a memory address. In other words, P stores a
memory address. At the memory address, stored by P, some value is stored that
may be an integer, a real number, a character or record of different data fields.

40.2 Exposure C++, Part IV, Abstract Data Types 12-08-99


In previous chapters I have talked about pointers and pointees. The pointer stores
the memory address, and the pointee is the value stored at the memory address. If
P is a pointer to some memory address that stores the integer value 1234, then *P
accesses the value 1234. Accessing the integer value is called dereferencing P.
In the event that P stores memory of some record, arrow notation is frequently
used to dereference each specific data filed like cout << P->Data;

So far, you have not seen an ounce of new information. In fact, dereferencing is
a good thing because you can access data fields in the nodes of a linked lists.
How else can a pointer traverse along a linked list without dereferencing? Totally
true, which gets us to the specific point that the problem occurs when the NULL
pointer is dereferenced.

Dereferencing the NULL Pointer Definition

A NULL pointer is dereferenced whenever an attempt is


made to access the expression *P, and P == NULL.

This definition may be satisfactory for many people, but for others it does not
clarify the problem. So let us look at some program examples and see why this is
an important issue. Program PROG4001.CPP starts with a simple program that
creates a linked list and traverses the linked list. For simplicity sake, the linked
list is ordered as a stack. All dereferencing in this program is correct and the
NULL pointer is not accessed in any improper fashion.

Pay particular attention to the manner in which NULL is used. You will see
NULL used to provide some initial pointer value. NULL is also used in the loop
condition of the while loop with the statement while(P != NULL) This
statement causes no problems. At no time is any attempt made to dereference our
hard working NULL pointer.

Please be aware that NULL is not a pointer to some special memory location that
sits around waiting to announce that the end of the road has been reached. NULL
is a constant, which equals zero. You could easily write all your linked list
programs and use the zero value to indicate the pointer value of the Next pointer
in the last node of the linked list.

// PROG4001.CPP
// This program creates and displays a linked list properly.
// The nodes are inserted in a LIFO sequence.

Chapter XL Linked Lists II 40.3


#include <iostream.h>
#include <stdlib.h>
#include <conio.h>

struct ListNode
{
int Data;
ListNode * Next;
};

void main()
{
clrscr();
ListNode * Temp = NULL;
ListNode * P = NULL;
int K, N;
cout << "Enter number of nodes in the list ===>> ";
cin >> N;
cout << endl;

// create a linked list of random integers


for (K = 1; K <= N; K++)
{
P = new ListNode;
P->Data = random(9000) + 1000;
cout << P->Data << " ";
P->Next = Temp;
Temp = P;
}
cout << endl << endl;

// traverse and display the data in the linked list


while (P != NULL)
{
cout << P->Data << " ";
P = P->Next;
}
getch();
}

PROG4001.CPP OUTPUT

Enter number of nodes in the list ===>> 8

1095 1035 4016 1299 4201 2954 5832 2761

2761 5832 2954 4201 1299 4016 1035 1095

40.4 Exposure C++, Part IV, Abstract Data Types 12-08-99


In the next program example I will do something that is a little strange. First, a
linked list is created that is linked in a LIFO sequence, just like the last program
example. After that segment is done, I get the urge to add a new node at the end
of the list. In this case that means the bottom of the stack since all previous nodes
were linked at the only end available with a convenient pointer. I know that the
very definition of a stack prevents the addition of a new element at the bottom.
This linked list is not a true stack; it is a linked list that is created in a stack
sequence. Please do not ask why somebody first creates a stack and wishes to
attach a node at the bottom.

The point is that we now have the need to get a pointer to the bottom node of the
existing list before it is possible to link anything there. In other words, we need
to traverse the list, and not until NULL is encountered, but rather until we reach
the last node. Now the whole point of using NULL is to identify the end of the
list. So what mechanism is available to identify the last node in the list?

The next program example shows the trick. This time traversing is done with the
statement while(Temp->Next != NULL). Our concern is not to compare
the traversing pointer, Temp, to NULL, but rather use Temp->Next, like the
program segment shows below.

// traverse the list so that Temp points at the last


// (bottom) node
while (Temp->Next != NULL)
Temp = Temp->Next;

Will that work? Check out program PROG4002.CPP and see if it works without
any problems. You may suspect that nothing should happen, but please have
patience I am trying hard to set the stage for this dereferencing problem.

// PROG4002.CPP
// This program creates a linked list in a LIFO sequence.
// A new node is linked at the bottom of the stack.
// The purpose of this program is to create a situation
// where dereferencing NULL might happen.

#include <iostream.h>
#include <stdlib.h>
#include <conio.h>

struct ListNode
{
int Data;
ListNode * Next;
};

Chapter XL Linked Lists II 40.5


void main()
{
clrscr();
ListNode * Temp = NULL;
ListNode * P = NULL;
ListNode * Head = NULL;
int K, N;

cout << "Enter number of nodes in the list ===>> ";


cin >> N;
cout << endl;

// create and display a linked list of random integers


for (K = 1; K <= N; K++)
{
P = new ListNode;
P->Data = random(9000) + 1000;
cout << P->Data << " ";
P->Next = Temp;
Temp = P;
}
Head = P;
cout << endl << endl;

// create a new node to be linked at the bottom of the stack


P = new ListNode;
P->Data = random(9000) + 1000;
P->Next = NULL;

// traverse the list so that Temp points at the last


// (bottom) node
while (Temp->Next != NULL)
Temp = Temp->Next;

// link the new node at the end of the list


Temp->Next = P;

// traverse and display the linked list


P = Head;
while (P != NULL)
{
cout << P->Data << " ";
P = P->Next;
}
getch();
}

PROG4002.CPP OUTPUT

Enter number of nodes in the list ===>> 8

1095 1035 4016 1299 4201 2954 5832 2761

2761 5832 2954 4201 1299 4016 1035 1095 7302

40.6 Exposure C++, Part IV, Abstract Data Types 12-08-99


It seems that a lot of noise was created over nothing because this program
executes very nicely without any problems. Do not get confused by the
execution. It is correct that the latest number, 7302, comes after 1095.
Remember that it is our intention to link the last node at the bottom of the “stack-
type” linked list.

Now here comes the basic question. What happens if the number of nodes
requested for the original list is zero? In the example above the original list
started with 8 nodes before one more node was added. Check to see what
happens to the execution sequence if the original list has zero nodes. The Temp
pointer is initialized to NULL. The whole loop business of creating a linked list
is ignored. A single node is created with full intentions to be linked at the bottom
of the “existing” list. The following loop will execute in an attempt to identify
the last node in the list.

// traverse the list so that Temp points at the last


// (bottom) node
while (Temp->Next != NULL)
Temp = Temp->Next;

Bingo . . . serious improper dereferencing is happening. The program statement


Temp->Next dereferences the Temp pointer and tries to access the Next field.
Now I will share a secret with you. Temp is still NULL and NULL does not
have a Next field, or any other field for that matter. NULL basically minds its
own business, chilling at the end of the list with a value of zero.

What happens when this horrible sin is committed? This is difficult to predict.
Different compilers will behave differently and even the same compilers may not
react the same way depending on when NULL is dereferenced. Absolutely
nothing may happen or the computer may freeze up. The point is that some
unpredictable result will follow, and we are not in the business of writing
programs that have unpredictable results.

It is not difficult to prevent these unpredictable problems. We just need to make


sure that it is not possible to dereference the NULL pointer. There are several
solutions to this problem and two solutions will be shown with the next two
program examples.

Chapter XL Linked Lists II 40.7


Program PROG4003.CPP uses a conditional statement that first checks to see if
the pointer equals NULL. Only if it is safe - when the pointer does not equal
NULL - the execution continues and dereferences the Temp pointer with the
statement while(Temp->Next != NULL).

// PROG4003.CPP
// This program serves the same purpose as the previous program.
// The big difference is that this program checks to see if Temp
// might be NULL. This check prevents "dereferencing" the
// NULL pointer.

#include <iostream.h>
#include <stdlib.h>
#include <conio.h>

struct ListNode
{
int Data;
ListNode * Next;
};

void main()
{
clrscr();
ListNode * Temp = NULL;
ListNode * P = NULL;
ListNode * Head = NULL;
int K, N;
cout << "Enter number of nodes in the list ===>> ";
cin >> N;
cout << endl;

// create and display a linked list of random integers


for (K = 1; K <= N; K++)
{
P = new ListNode;
P->Data = random(9000) + 1000;
cout << P->Data << " ";
P->Next = Temp;
Temp = P;
}
Head = P;
cout << endl << endl;

// create a new node to be linked at the end of the list


P = new ListNode;
P->Data = random(9000) + 1000;
P->Next = NULL;

// check if Temp is NULL, which prevents potential


// dereferencing NULL

40.8 Exposure C++, Part IV, Abstract Data Types 12-08-99


if (Temp != NULL)
{
// traverse the list so that Temp points at the last node
while (Temp->Next != NULL)
Temp = Temp->Next;

// link the new node at the end of the list


Temp->Next = P;
}

// traverse and display the linked list


P = Head;
while (P != NULL)
{
cout << P->Data << " ";
P = P->Next;
}
getch();
}

PROG4003.CPP OUTPUT

Enter number of nodes in the list ===>> 8

1095 1035 4016 1299 4201 2954 5832 2761

2761 5832 2954 4201 1299 4016 1035 1095 7302

The output execution is identical to the previous program without any special
dereference protection. This is logical because both executions use an initial list
of eight nodes. The second program will work much better if the initial list is
zero. Now the program still does not work perfectly since it is not meant to add a
new node to a non-existing list. However, the main point is that the program will
not crash and result in peculiar computer behavior.

The second solution relies on the unique property of C++ which short circuits
compound conditions based on the value of the first condition. This topic was
handled in the first course in a special Boolean Algebra chapter. Look at the next
program example. Do you recognize the short circuit approach?

// PROG4004.CPP
// This program creates a linked in a FIFO sequence and then
// traverses and display all the node values except for the
// last one. This demonstrates how dereferencing can be
// avoided by using a compound Boolean condition that will

Chapter XL Linked Lists II 40.9


// "short circuit" before dereferencing the NULL pointer is a
// possibility.

#include <iostream.h>
#include <stdlib.h>
#include <conio.h>

struct ListNode
{
int Data;
ListNode * Next;
};

void main()
{
clrscr();
ListNode * Front;
ListNode * Temp = NULL;
ListNode * P = NULL;
int K, N;
cout << "Enter number of nodes in the list ===>> ";
cin >> N;
cout << endl;

// create the front node


Temp = new ListNode;
Temp->Data = random(9000) + 1000;
Temp->Next = NULL;
cout << Temp->Data << " ";
Front = Temp;

// create and display the remaining linked list of integers


for (K = 2; K <= N; K++)
{
P = new ListNode;
P->Data = random(9000) + 1000;
P->Next = NULL;
cout << P->Data << " ";
Temp->Next = P;
Temp = P;
}
cout << endl << endl;

// traverse the linked list using short-circuit protection


// the last node is intentionally not displayed
P = Front;
while (P != NULL && P->Next != NULL)
{
cout << P->Data << " ";
P = P->Next;
}
getch();
}

40.10 Exposure C++, Part IV, Abstract Data Types 12-08-99


PROG4004.CPP OUTPUT

Enter number of nodes in the list ===>> 8

1095 1035 4016 1299 4201 2954 5832 2761

1095 1035 4016 1299 4201 2954 5832

The compound statement while(P != NULL && P->Next != NULL)


accomplishes the same goal as the if(P != NULL) statement of the previous
program. The compound condition checks to see if P is a NULL pointer. If that
first condition is false then the compound and statement is guaranteed to be false
and C++ does not bother to process the second condition. This is an example of
using the C++ short circuiting feature, and it prevents the possibility of
dereferencing the NULL with the second condition. You were first introduced to
the lazy tendencies of C++ compilers, by not evaluating certain complete
conditions, way back in Chapter 11.

APCS Examination Alert

Dereferencing the NULL pointer is a definite issue on the


APCS exam. You can count on writing several functions
That manipulate linked structures in some manner.

Many students lose points with their solutions because


There is no protection provided against the dreaded
dereferencing problem.

40.3 Stack Library Functions

Several chapters back you were introduced to the stack abstract data structure.
You learned about the stack in an abstract sense, used the provided apstack library
of functions and also created a set of stack library functions for yourself. The

Chapter XL Linked Lists II 40.11


implementations of the stack data structure in the earlier chapter revolved around
using a record with an array field to store stack elements and an integer field to
store the index of the top stack element.

We now have an excellent opportunity to see that an abstract data structure is


truly abstract and that there are many implementations that serve the precise same
purpose. In this section you will see how the concepts of the previous chapter can
be made more practical by creating a set of functions. Specifically, in the
previous chapter you learned how to create a linked list that linked every new
node at one end such that the linked list behaves like a stack. The program code
to do all this linking business is not all that easy and our life can be simplified by
creating some user-friendly functions. Program PROG4005.CPP tests four stack
functions. Also note the syntax used to pass a pointer by reference.

// PROG4005.CPP
// This program implements a stack data structure library
// of functions implemented with a dynamic linked list.
// The parameter style of using both an asterisk and an
// ampersand is the style expected for the AP examination.

#include <iostream.h>
#include <conio.h>
#include "BOOL.H"

struct StackNode
{
int Data;
StackNode * Next;
};

// stack function prototypes

void ClearStack(StackNode * &Top);


// initializes a stack with zero elements

bool EmptyStack(StackNode * Top);


// returns true if stack has zero elements, false otherwise

void Push(StackNode * &Top, int StackItem);


// adds StackItem to the top of the stack

void Pop(StackNode * &Top, int &StackItem);


// removes the top element and assign it to Stack Item

// function prototypes used to test the stack library

void FillStack(StackNode * &Top);


// adds new values to the stack and builds the linked structure

40.12 Exposure C++, Part IV, Abstract Data Types 12-08-99


void ShowStack(StackNode * &Top);
// displays the values stored in the stack

void main()
{
clrscr();
StackNode * TopPtr;
ClearStack(TopPtr);
FillStack(TopPtr);
ShowStack(TopPtr);
getch();
}

/////////////////////////////////////////////////////////////////
// function implementations that check the stack library

void FillStack(StackNode * &Top)


{
int StackItem;
for (StackItem = 1; StackItem <= 20; StackItem++)
{
cout << StackItem << " ";
Push(Top,StackItem);
}
}

void ShowStack(StackNode * &Top)


{
cout << endl << endl;
int StackItem;
while (!EmptyStack(Top))
{
Pop(Top,StackItem);
cout << StackItem << " ";
}
}

/////////////////////////////////////////////////////////////////
// stack function implementations

void ClearStack(StackNode * &Top)


{
Top = NULL;
}

Chapter XL Linked Lists II 40.13


bool EmptyStack(StackNode * Top)
{
return Top == NULL;
}

void Push(StackNode * &Top, int StackItem)


{
StackNode * Temp;
Temp = new StackNode;
Temp->Data = StackItem;
Temp->Next = Top;
Top = Temp;
}

void Pop(StackNode * &Top, int &StackItem)


// this function may potentially dereference the NULL pointer
{
StackNode * Temp;
Temp = Top;
StackItem = Top->Data;
Top = Top->Next;
delete Temp;
}

PROG4005.CPP OUTPUT

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1

Now that you are fresh from a thou shalt not dereference the NULL pointer
section, do you recognize that function Pop has some potential problems. Calling
Pop any time that the Top pointer equals NULL will result in dereferencing fun.
In the next program example you will see an improved Pop function and this
program also uses typedef to make the syntax of pointer parameter passing more
readable and user-friendly.

// PROG4006.CPP
// This program implements a stack data structure library
// of functions with a dynamic linked list like the last program.
// This version uses a typedef to simplify parameter declarations.
// Function Pop is improved by preventing potential dereferencing.

#include <iostream.h>
#include <conio.h>
#include "BOOL.H"

40.14 Exposure C++, Part IV, Abstract Data Types 12-08-99


struct StackNode
{
int Data;
StackNode * Next;
};

typedef StackNode * StackPtr;

// stack function prototypes

void ClearStack(StackPtr &Top);


// initializes a stack with zero elements

bool EmptyStack(StackPtr Top);


// returns true if stack has zero elements, false otherwise

void Push(StackPtr &Top, int StackItem);


// adds StackItem to the top of the stack

void Pop(StackPtr &Top, int &StackItem);


// removes the top element and assign it to Stack Item

// function prototypes used to test the stack library


void FillStack(StackPtr &Top);

void ShowStack(StackPtr &Top);

void main()
{
clrscr();
StackPtr TopPtr;
ClearStack(TopPtr);
FillStack(TopPtr);
ShowStack(TopPtr);
getch();
}

/////////////////////////////////////////////////////////////////
// function implementations that check the stack library

void FillStack(StackPtr &Top)


{
int StackItem;
for (StackItem = 1; StackItem <= 20; StackItem++)
{
cout << StackItem << " ";
Push(Top,StackItem);
}
}

Chapter XL Linked Lists II 40.15


void ShowStack(StackPtr &Top)
{
cout << endl << endl;
int StackItem;
while (!EmptyStack(Top))
{
Pop(Top,StackItem);
cout << StackItem << " ";
}
}

/////////////////////////////////////////////////////////////////
// stack function implementations

void ClearStack(StackPtr &Top)


{
Top = NULL;
}

bool EmptyStack(StackPtr Top)


{
return Top == NULL;
}

void Push(StackPtr &Top, int StackItem)


{
StackPtr Temp;
Temp = new StackNode;
Temp->Data = StackItem;
Temp->Next = Top;
Top = Temp;
}

// Improved Pop function that avoids potential dereferencing


void Pop(StackPtr &Top, int &StackItem)
{
if (Top != NULL)
{
StackPtr Temp;
Temp = Top;
StackItem = Top->Data;
Top = Top->Next;
delete Temp;
}
}

PROG4006.CPP OUTPUT

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1

40.16 Exposure C++, Part IV, Abstract Data Types 12-08-99


AP Examination Alert for Passing Pointers by Reference

Consider the following declaration:

struct StackNode
{
int Data;
StackNode * Next;
};

Using typedef for syntax simplicity


typedef StackNode * StackPtr;

void ClearStack(StackPtr &Top)


{
Top = NULL;
}

Note: This Approach is Used for the AP Examination


void ClearStack(StackNode * &Top)
{
Top = NULL;
}

40.4 Queue Library Functions

When it comes to creating a queue library of functions - using linked lists - you
will now realize that working with queues does not have the special complication
that was experienced with earlier queue functions. In our previous queue chapter
we implemented queues with a record that used an array to store the queue
elements, along with an integer value for the front and rear index of the queue.
This approach required special wrap-around techniques because the array is a
linear list. There exists no such problem with the approach shown in program
PROG4007.CPP. Note that the DeQueue function - like the Pop function - uses
the conditional statement to make sure that NULL is not dereferenced.

Chapter XL Linked Lists II 40.17


// PROG4007.CPP
// This program implements a queue data structure library
// of functions with a dynamic linked list.

#include <iostream.h>
#include <conio.h>
#include "BOOL.H"

struct QueueNode
{
int Data;
QueueNode * Next;
};

typedef QueueNode * QueuePtr;

// queue function prototypes

void ClearQueue(QueuePtr &Front, QueuePtr &Back);


// initializes a queue with zero elements

bool EmptyQueue(QueuePtr Front);


// returns true if stack has zero elements, false otherwise

void EnQueue(QueuePtr &Back, int QueueItem);


// adds QueueItem to the back of the queue

void DeQueue(QueuePtr &Front, int &QueueItem);


// removes the front element of the queue, and assigns it
// to QueueItem

// function prototypes used to test the queue library

void FillQueue(QueuePtr &FrontPtr, QueuePtr &BackPtr);


void ShowQueue(QueuePtr &FrontPtr);

void main()
{
clrscr();
QueuePtr FrontPtr;
QueuePtr BackPtr;
ClearQueue(FrontPtr,BackPtr);
FillQueue(FrontPtr,BackPtr);
ShowQueue(FrontPtr);

40.18 Exposure C++, Part IV, Abstract Data Types 12-08-99


}

/////////////////////////////////////////////////////////////////
// queue function implementations

void ClearQueue(QueuePtr &Front, QueuePtr &Back)


{
Front = NULL;
Back = NULL;
}

bool EmptyQueue(QueuePtr Front)


{
return Front == NULL;
}

void EnQueue(QueuePtr &Back, int QueueItem)


{
QueuePtr Temp;
Temp = new QueueNode;
Temp->Data = QueueItem;
Temp->Next = NULL;
if (Back != NULL)
Back->Next = Temp;
Back = Temp;
}

void DeQueue(QueuePtr &Front, int &QueueItem)


{
if (Front != NULL)
{
QueuePtr Temp;
Temp = Front;
QueueItem = Front->Data;
Front = Front->Next;
delete Temp;
}
}

/////////////////////////////////////////////////////////////////
// function implementations that check the queue library

void FillQueue(QueuePtr &FrontPtr, QueuePtr &BackPtr)


{
int QueueItem = 1;
cout << QueueItem << " ";
EnQueue(BackPtr,QueueItem);

Chapter XL Linked Lists II 40.19


FrontPtr = BackPtr;
for (QueueItem = 2; QueueItem <= 20; QueueItem++)
{
cout << QueueItem << " ";
EnQueue(BackPtr,QueueItem);
}
}

void ShowQueue(QueuePtr &FrontPtr)


{
cout << endl << endl;
int QueueItem;
while (!EmptyQueue(FrontPtr))
{
DeQueue(FrontPtr,QueueItem);
cout << QueueItem << " ";
}
}

PROG4007.CPP OUTPUT

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

The stack and queue libraries shown here are by no means complete. I have not
presented as many functions as the apstack class and the apqueue class provide
for you. This is intentional because my interest is in showing how to use linked
lists for various situations. It is not the intention of this chapter to do an
exhaustive treatment on every data structure. Hopefully, you have the tools to
create more functions if you have the need for any special situation. C++ never
tried to be everything to everybody, but provide you with the tools to create any
data structure and functions needed for any purpose.

40.5 Deallocating Memory Dynamically

What is the big advantage of allocating memory dynamically? It is not necessary


to play guessing games about the potential memory needs of a data structure
before program execution. Allow the program, and the program user to

40.20 Exposure C++, Part IV, Abstract Data Types 12-08-99


determine how much space is necessary and grab that space when it is needed.
Now what is the big advantage of creating linked lists dynamically? With linked
lists we go up another notch in practical memory usage. It is now no longer
necessary to allocate contiguous memory space. The computer grabs all the space
needed for the newly allocated node and the program instructions (hopefully) link
this new node in the proper sequence of the existing linked list.

This linked approach allows the creation of very large data structures even though
your computer may not have any large chunks of memory floating around. This
is all very lovely, but with very little imagination this dynamic memory allocation
business can come down to a crashing halt in a very big hurry. The problem is
that once memory is allocated it is now reserved for whatever purpose it was
allocated. Now it may be used or not but no future new operation can take that
memory away. It is reserved, plain and simple.

Picture the following realistic situation. Some program retrieves data from an
external file and stores the data in a linked list for processing requirements. This
process continues and every time a different file is retrieved and stored in a
different linked list. At some point the processing with the first linked list is no
longer needed, but the allocated memory is still reserved. Sooner or later the
continuous process of allocating memory for a new linked list exhausts all the
available memory in the computer. At this point the computer will freeze up,
crash, courtesy reboot, or perform some other undesirable ritual. It is vitally
important to write programs that prevent such problems.

Any program that incorporates allocating memory dynamically must also


deallocate memory dynamically. When does the memory need to be deallocated?
Whenever the data does not require storage and the program moves on to some
other application. Program PROG4008.CPP creates a linked list and calls
function DeleteList to return the allocated memory.

// PROG4008.CPP
// This program demonstrates how to deallocate memory dynamically.

#include <iostream.h>
#include <conio.h>

struct ListNode
{
int Data;
ListNode * Next;
};

typedef ListNode * ListPtr;

Chapter XL Linked Lists II 40.21


void CreateList(ListPtr &HeadPtr);
void ShowList(ListPtr HeadPtr);
void DeleteList(ListPtr &HeadPtr);

void main()
{
clrscr();
ListPtr HeadPtr;
CreateList(HeadPtr);
ShowList(HeadPtr);
DeleteList(HeadPtr);
getch();
}

void CreateList(ListPtr &HeadPtr)


{
int ListItem = 1;
ListPtr T1,T2;
T1 = new ListNode;
T1->Data = ListItem;
cout << T1->Data << " ";
T1->Next = NULL;
T2 = T1;
HeadPtr = T1;
for (ListItem = 2; ListItem <= 20; ListItem++)
{
T1 = new ListNode;
T1->Data = ListItem;
cout << T1->Data << " ";
T1->Next = NULL;
T2->Next = T1;
T2 = T1;
}
}

void ShowList(ListPtr P)
{
cout << endl << endl;
while (P != NULL)
{
cout << P->Data << " ";
P = P->Next;
}
}

void DeleteList(ListPtr &P)


{
ListPtr Temp;

40.22 Exposure C++, Part IV, Abstract Data Types 12-08-99


while (P != NULL)
{
Temp = P;
P = P->Next;
delete Temp;
}
cout << endl << endl;
cout << "MEMORY IS DEALLOCATED" << endl;
}

PROG4008.CPP OUTPUT

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

MEMORY IS DEALLOCATED

Returning memory as was just shown is by no means the only method or the most
desirable method of returning memory. There are a variety of approaches to this
problem. In this section the main concern is to create awareness for a very
important aspect of dynamic memory allocation, which is controlling the
available memory for future use.

Manage Dynamic Memory Properly

Only allocate as much memory as is reasonably needed.

Make sure that all allocated memory is returned as soon


as the program does not need the memory anymore.

If you write the small programs required for each chapter’s lab assignment, it is
doubtful that you will find any problems. Problems, that is, by failing to return
allocated memory. Anytime that a program performs intensive data processing,
and especially when the data is retrieved from large external files, problems will
happen quickly. Follow this advise closely. It is a very common cause of
program crashes when students start to work on their large projects.

40.6 The MyStack Class

Chapter XL Linked Lists II 40.23


Perhaps you wonder why there seems to be this odd jumping back and forth
between OOP and No-OOP style programming. OOP was introduced a long time
ago and it was certainly introduced as having lots of nifty advantages. OOP is
good and OOP should be used. So why are there so many examples without OOP
anywhere in sight? This approach is intentional. Even though you have already
learned many OOP features, the OOP chapter is not closed and will steadily
continue. Many new concepts are easier to present in an isolated environment
where the total focus is on the new concept. Linked lists is a classic example.
You have learned the abstract logic as well as the implementation syntax of
linked list processes without any class declarations.

Now that you have a good linked list introduction behind you, the time has come
to add OOP to the story and see how it plays out. It is very well possible that
many students will take a so what attitude and conclude that object oriented
programming and linked lists are no big deal. Encapsulate the functions in the
class declaration, change the syntax by adding some funky scope resolution
operators and you are mostly there. If you feel that way, terrific. For many
students a longer, two-step approach is simpler.

APCS Examination Alert

It is anticipated that APCS Examination will test knowledge


about dynamic memory allocation with linked data structures
without using object oriented programming.

This chapter will present both the non-oop approach, which


was shown up to this point, and the oop approach, which
will be shown next.

Program PROG4009.CPP shows a stack library with the same set of functions as
the previous library. In this program example all the stack functions are
encapsulated in the MyStack class. This program example does not show any
new concepts. It combines the object oriented programming style, which you
learned earlier with the recent linked lists concepts in one program. Object
oriented programming is not meant to be something that is used now and then. It
is a style of programming that impacts program development tremendously at
every level. Keep in mind that there is a major difference between learning
computer science concepts and using computer science concepts. The object

40.24 Exposure C++, Part IV, Abstract Data Types 12-08-99


oriented approach is our goal. Check out this OOP approach in the next program
example, and see if there are any advantages in this approach.

// PROG4009.CPP
// This program encapsulates the stack library functions
// in a MyStack class.

#include <iostream.h>
#include <conio.h>
#include "BOOL.H"

// declaration of a stack data structure using a linked list

class MyStack
{
public:
MyStack();
~MyStack();
void ClearStack();
bool EmptyStack();
void Push(int StackItem);
void Pop(int &StackItem);

private:
struct StackNode
{
int Data;
StackNode * Next;
};
StackNode * Top;
};

// free function prototypes to test the MyStack class

void FillStack(MyStack &S);

void ShowStack(MyStack &S);

void main()
{
clrscr();
MyStack S;
FillStack(S);
ShowStack(S);
getch();
}

Chapter XL Linked Lists II 40.25


/////////////////////////////////////////////////////////////////
// implementations of the free functions

void FillStack(MyStack &S)


{
int StackItem;
S.ClearStack();
for (StackItem = 1; StackItem <= 20; StackItem++)
{
cout << StackItem << " ";
S.Push(StackItem);
}
}

void ShowStack(MyStack &S)


{
cout << endl << endl;
int StackItem;
while (!S.EmptyStack())
{
S.Pop(StackItem);
cout << StackItem << " ";
}
}

/////////////////////////////////////////////////////////////////
// implementations of the Mystack class member functions

MyStack::MyStack()
{
Top = NULL;
}

MyStack::~MyStack()
{
StackNode * Temp;
while (Top != NULL)
{
Temp = Top;
Top = Top->Next;
delete Temp;
}
}

void MyStack::ClearStack()
{
Top = NULL;
}

bool MyStack::EmptyStack()
{

40.26 Exposure C++, Part IV, Abstract Data Types 12-08-99


return Top == NULL;
}

void MyStack::Push(int StackItem)


{
StackNode * Temp;
Temp = new StackNode;
Temp->Data = StackItem;
Temp->Next = Top;
Top = Temp;
}

void MyStack::Pop(int &StackItem)


{
if (Top != NULL)
{
StackNode * Temp;
Temp = Top;
StackItem = Top->Data;
Top = Top->Next;
delete Temp;
}
}

PROG4009.CPP OUTPUT

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1

If you were strictly focused on the program code inside each stack function, then
you would see no difference. The logic and the program statements of Push, Pop
and the other stack functions do not change a class declaration. What does
change is that everything connected with the stack library is neatly contained
within the MyStack class declaration. With that approach we gain readability
and we also increase reliability. How is reliability improved?

Reliability is improved two ways and both are connected to some major aspects
of object oriented programming. The stack data structure will not work properly
unless a new stack starts out empty, ready for business. This means that a
function like makeEmpty or Clearstack or what you select to call it, makes sure
that a new stack is ready for business and the stack is totally empty.

This is all very nice, but it assumes that the person using the stack library is
clever enough to clear any newly created stack. With object oriented

Chapter XL Linked Lists II 40.27


programming we now see a very good purpose for the constructor. The Mystack
class has a constructor which insures that any newly constructed stack will be
empty. Since the constructor is automatically called anytime that a new stack
object is defined, it is then guaranteed that the stack object starts out ready for
business. This is a major advantage with using object oriented programming.

At the tail end of the stack usage, when the object leaves its scope, the destructor
is called automatically. In previous object oriented programming chapters, it has
been difficult to demonstrate practical examples for using a destructor. Well, we
have arrived because the evils of forgetting to deallocate memory can now be
avoided automatically.

You just make sure that the destructor returns any allocated memory correctly and
the rest is done during program execution. In the previous program example a
special function was called to delete any unused memory. With the class
declaration of MyStack such a function is totally unnecessary. The MyStack
destructor does that job ever so nicely.

40.7 The MyQueue Class

This section is a continuation of the previous Mystack class section. It should


come as no big surprise that the two advantages of using a class for a stack library
also apply to a queue library that is implemented as a class.

If you look closely at program PROG4010.CPP you may see some other
advantages as well that may not have been so apparent with the stack library.

// PROG4010.CPP
// This program encapsulates the queue library functions
// in a MyQueue class.

#include <iostream.h>
#include <conio.h>
#include "BOOL.H"

// declaration of a queue data structure using a linked list


class MyQueue

40.28 Exposure C++, Part IV, Abstract Data Types 12-08-99


{
public:
MyQueue();
~MyQueue();
void ClearQueue();
bool EmptyQueue() const;
void EnQueue(int QueueItem);
void DeQueue(int &QueueItem);
private:
struct QueueNode
{
int Data;
QueueNode * Next;
};
QueueNode * Front;
QueueNode * Rear;
bool First;
};

// free function prototypes to test the MyQueue class


void FillQueue(MyQueue &Q);
void ShowQueue(MyQueue &Q);

void main()
{
clrscr();
MyQueue Q;
FillQueue(Q);
ShowQueue(Q);
getch();
}

/////////////////////////////////////////////////////////////////
// implementations of the free functions

void FillQueue(MyQueue &Q)


{
int QueueItem;
Q.ClearQueue();
for (QueueItem = 1; QueueItem <= 20; QueueItem++)
{
cout << QueueItem << " ";
Q.EnQueue(QueueItem);
}
}

void ShowQueue(MyQueue &Q)


{
cout << endl << endl;
int QueueItem;
while (!Q.EmptyQueue())
{

Chapter XL Linked Lists II 40.29


Q.DeQueue(QueueItem);
cout << QueueItem << " ";
}
}

/////////////////////////////////////////////////////////////////
// implementations of the MyQueue class member functions
// note the special jobs that are performed by the constructor
// and the destructor to make the class more reliable.

MyQueue::MyQueue()
{
Front = NULL;
Rear = NULL;
First = true;
}

MyQueue::~MyQueue()
{
QueueNode * Temp;
while (Front != NULL)
{
Temp = Front;
Front = Front->Next;
delete Temp;
}
}

void MyQueue::ClearQueue()
{
Front = NULL;
Rear = NULL;
}

bool MyQueue::EmptyQueue() const


{
return Front == NULL;
}

void MyQueue::EnQueue(int QueueItem)


{
QueueNode * Temp;
Temp = new QueueNode;
Temp->Data = QueueItem;
Temp->Next = NULL;
if (First)
{
Front = Temp;
Rear = Temp;
First = false;
}
else
{

40.30 Exposure C++, Part IV, Abstract Data Types 12-08-99


Rear->Next = Temp;
Rear = Temp;
}
}

void MyQueue::DeQueue(int &QueueItem)


{
if (Front != NULL)
{
QueueNode * Temp;
Temp = Front;
QueueItem = Front->Data;
Front = Front->Next;
delete Temp;
}
}

PROG4010.CPP OUTPUT

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

You remember that the queue has some extra baggage that makes queue
operations more complicated to implement than stack operations. First there is
the business that a Front pointer needs to anchor the first node of the linked list
while two other pointers build the list at the rear end. With a class it is easy to
store a Boolean First variable that is initialized to true by the constructor when a
new queue object is defined. The EnQueue function can change that variable to
false as soon as the first node is created. This is a clean operation and there are
no problems with how to deal with that first node.

Another advantage is the parameter business. The queue library presented earlier
in this chapter needed to pass both the Front pointer and the Rear pointer along
with a parameter for the queue data structure. This type of parameter passing is
quite messy and can be avoided so easily with a class. Both Front and Rear are
private data that is stored in the class along with First. Calling any of the
functions is now much simpler and the only parameters in sight are the ones used
to add a new element to the queue or to remove an element from a queue.

Chapter XL Linked Lists II 40.31


40.8 Doubly Linked Lists

Every linked list that has been demonstrated so far, whether linked in a stack,
queue, or ordered sequence, had one pointer per node. Furthermore, every
pointer always linked in one direction. In other words, it is possible to traverse a
linked list from the front to the rear, or the top to the bottom, but not from the
rear back to the front. There are times when it is not practical to have a data
structure that has such inflexibility. You need the advantages that pointers allow
but at the same time you want the ability to move along a linked list in both
directions.

The solution? Well . . . it is a doubly linked list that comes to the rescue. There
is no requirement that limits a linked node to one pointer per node. In all the
previous linked list examples the single pointer in each node has been called
Next. If we place two pointer fields in a node, then we create the ability to link
the pointers in two opposite directions.

APCS Examination Alert

A survey of previously released free-response examinations


shows that doubly-linked lists are a frequent question topic
on the APCS examination.

Most of the previous exams were in Pascal, but the question


philosophy for an abstract data structure does not change
with the switch to a new programming language.

In the next program example we will not be using functions to demonstrate the
doubly linking process. Program PROG4011.CPP creates a linked list that can
be traversed in both directions.

// PROG4011.CPP
// This program demonstrates how to create a doubly linked list.

#include <iostream.h>
#include <conio.h>

40.32 Exposure C++, Part IV, Abstract Data Types 12-08-99


#include "BOOL.H"

struct DoubleNode
{
int Data;
DoubleNode * Fwrd;
DoubleNode * Back;
};

void main()
{
clrscr();
DoubleNode * FrontPtr; // points to front of list
DoubleNode * BackPtr; // points to back of the list
DoubleNode * Temp1; // pointer used to built the list
DoubleNode * Temp2; // pointer used to built the list
int K;

// creating the first node


Temp1 = new DoubleNode;
Temp1->Data = 100;
FrontPtr = Temp1;
BackPtr = Temp1;
FrontPtr->Fwrd = NULL;
BackPtr->Back = NULL;
Temp2 = Temp1;

// creating 9 nodes linked in both directions


for (K = 200; K <= 900; K+=100)
{
Temp1 = new DoubleNode;
Temp1->Data = K;
Temp1->Fwrd = NULL;
BackPtr = Temp1;
Temp2->Fwrd = Temp1;
Temp1->Back = Temp2;
Temp2 = Temp1;
}

// Traverse list from front to back


Temp1 = FrontPtr;
while (Temp1 != NULL)
{
cout << Temp1->Data << " ";
Temp1 = Temp1->Fwrd;
}
cout << endl << endl;

// Traverse list from back to front


Temp2 = BackPtr;
while (Temp2 != NULL)
{
cout << Temp2->Data << " ";

Chapter XL Linked Lists II 40.33


Temp2= Temp2->Back;
}
getch();
}

PROG4011.CPP OUTPUT

100 200 300 400 500 600 700 800 900

900 800 700 600 500 400 300 200 100

Did you perhaps not grasp every detail of the last program example? Well not to
worry because we will now look at each component of the program and digest the
logic in small chunks. Starting with the DoubleNode declaration you will notice
the same basic style of previous linked list declarations. The only difference is
that Next has been replaced with two pointers, Fwrd and Back.

struct DoubleNode
{
int Data;
DoubleNode * Fwrd;
DoubleNode * Back;
};

The first program segment of the program involves declaring pointers and setting
up the first node of the doubly linked list.

DoubleNode * FrontPtr; // (01)


DoubleNode * BackPtr; // (02)
DoubleNode * Temp1; // (03)
DoubleNode * Temp2; // (04)
int K;
Temp1 = new DoubleNode; // (05)
Temp1->Data = 100; // (06)
FrontPtr = Temp1; // (07)
BackPtr = Temp1; // (08)
FrontPtr->Fwrd = NULL; // (09)
BackPtr->Back = NULL; // (10)

(01) Declares FrontPtr as a pointer to DoubleNode


(02) Declares BackPtr as a pointer to DoubleNode
(03) Declares Temp1 as a pointer to DoubleNode used for building the list

40.34 Exposure C++, Part IV, Abstract Data Types 12-08-99


(04) Declares Temp2 as a pointer to DoubleNode used for building the list
(05) A DoubleNode memory cell is allocated with Temp1 pointing at it
(06) 100 is stored in the Data field of the new node
(07) FrontPtr is assigned to the first node
(08) BackPtr is assigned to the first node
(09) The Fwrd pointer is assigned NULL
(10) The Back pointer is assigned NULL

If you look at these steps closely you will realize that the logic of setting up the
first node of the linked list is the same as you learned with earlier singly linked
lists. There are more steps because both directions of the list need to have the end
of the list indicated by NULL. The diagram below illustrates the result of the
first ten program statements. Fr is the FrontPtr, Bk is the BackPtr, T1 is the
Temp1 pointer and T2 is the Temp2 pointer.

[Fr] [Bk]
[T1] A
[T2]

In the next program segment 8 nodes will be linked for a total of 9 nodes that will
store multiples of 100. Pay close attention to the linking details, which are quite a
bit more complex with a doubly linked list.

Temp2 = Temp1; // (11)

// creating 9 nodes linked in both directions


for (K = 200; K <= 900; K+=100)
{
Temp1 = new DoubleNode; // (12)
Temp1->Data = K; // (13)
Temp1->Fwrd = NULL; // (14)
BackPtr = Temp1; // (15)
Temp2->Fwrd = Temp1; // (16)
Temp1->Back = Temp2; // (17)
Temp2 = Temp1; // (18)
}

(11) Temp2 is assigned to Temp1. This sets up the pattern of the trailing
pointer that allows two nodes to be linked.
(12) Temp1 points to a newly created node
(13) The next integer value is stored in the Data field of the new node
(14) Temp1->Fwrd is assigned to NULL
(15) The BackPtr is shifted to the newly constructed back-end node

Chapter XL Linked Lists II 40.35


(16) Temp2->Fwrd is assigned to Temp1, which links the nodes front to back
(17) Temp1->Back is assigned to Temp2, which links the nodes back to front
(18) Temp2 moves up one node to get ready for the next linking

Did you understand why only one pointer is assigned to NULL inside the loop,
even though there are two sets of pointers? The list is created in a queue
sequence, which means that new nodes are linked at the back of the existing list.
The very first node requires that the Back pointer is assigned NULL, but after
that first node, it is only the Fwrd pointer that is assigned NULL with the
creation of each node. The diagram, on the next page, shows the result after the
loop body has executed one time.

If you followed the advice of these chapters, and the advice of your teacher, then
you know the importance of drawing pictures when you work with linked lists.
This was important when there was only one set of pointers. Now that pointers
go in two directions, the use of pictures and clear diagrams becomes absolutely
vital in the design of accurate programs.

The next time through the loop, Temp1 creates another node. Linking is made
possible because Temp2 is left behind at the previously created node. The
FrontPtr anchors one end of the list for traversing left to right and the BackPtr
anchors the other end of the list for traversing right to left.

[T2] [T1]

[Bk]
A B
[Fr]

Please keep in mind that the left-to-right and right-to-left linking is figuratively
speaking. The illustration is strictly an abstract, symbolic representation of the
pointer objects. It does not matter if the pointers point to the left side of the
object or the right side or the top or bottom of the drawing.

I am going to try to represent a more realistic view of the previous linked list.
The next page shows a diagram that represents memory cells. Each cell is
identified by a base 16 memory address like @7b00. The @ indicates address in
this illustration. Each memory cell is six bytes. Two bytes for each pointer and
two bytes for the integer data field.

40.36 Exposure C++, Part IV, Abstract Data Types 12-08-99


As was mentioned before not every compiler allocates memory in the exact same
manner. The importance here is to visualize a memory grid of addresses drawn
such that every cell can hold the information of one node in the doubly linked list.
If every actual memory address is drawn, the illustration will likely become
messy and confusing. Now keep in mind that there are also two pointers,
FrontPtr and BackPtr that need to be stored somewhere.

A pointer may store a memory address, but this memory address in turn needs to
be stored somewhere. The space requirement for these two pointers is technically
only two bytes. The illustration, on the next page, may give the impression that
six bytes are needed which is not true.

@7b00 @7b06 @7b0c @7b12 @7b18 @7b1e @7b24 @7b2a

@7b78

@7b30 @7b36 @7b3c @7b42 @7b48 @7b4e @7b54 @7b5a

@7b90

@7b60 @7b66 @7b6c @7b72 @7b78 @7b7e @7b84 @7b8a


@7b90
100
@0000
@7b90 @7b96 @7b9c @7ba2 @7ba8 @7bae @7bb4 @7bba
@0000
200
@7b78

Now to interpret this illustration properly and see what is going on here:

FrontPtr is located at @7b0cb and stores the address of the first node, @7b78.
At @7b78b the Fwrd pointer field stores @7b90 for the next node, 100 for
the Data field and @0000 for the Back pointer field, which is NULL.
At @7b90 the Fwrd pointer stores @0000, for the next node, which is NULL,

Chapter XL Linked Lists II 40.37


200 for the Data field and @7b78 for the Back pointer field to the previous
node.
BackPtr is located at @7b4e and stores the address of the last node, @7b90.

Now let us return to the final part of the doubly linked list program. Assume that
the loop has finished iterating. Nine nodes have been created, and linked in both
directions. There is now a starting point at both ends, and there is NULL at both
ends of the list to stop traversing in either direction. The remaining code
traverses the list in both directions to test the doubly linked list.

// Traverse list from front to back


Temp1 = FrontPtr; // (19)
while (Temp1 != NULL) // (20)
{
cout << Temp1->Data << " "; // (21)
Temp1 = Temp1->Fwrd; // (22)
}
cout << endl << endl;

// Traverse list from back to front


Temp2 = BackPtr; // (23)
while (Temp2 != NULL) // (24)
{
cout << Temp2->Data << " "; // (25)
Temp2= Temp2->Back; // (26)
}

(19) Temp1 is assigned to FrontPtr. This is a very significant step. If


traversing were to be done with FrontPtr, then access to the front of the
list is disabled after one traversal. Multiple traversals are now possible.
(20) The while loop is entered until Temp1 equals NULL
(21) The value of the Data field is displayed
(22) The Temp1 pointer traverses to the next node

40.38 Exposure C++, Part IV, Abstract Data Types 12-08-99


Steps (23) - (26) repeat the precise same process in reverse order starting at the
back end of the list and traversing forward. Note that the same precaution is
taken by using Temp2 to traverse back and not BackPtr. It is the job of
FrontPtr and BackPtr to solidly anchor both ends of the list. Changing the
value of either one of these pointers is only allowed if either the front or the back
node in the list changes.

40.9 Circular Linked Lists

Computer science never ceases to amaze you. There are always other structures
and goodies to be added to your cerebral enjoyment. In this section we are going
to make an extremely minor change to the program, which created a linked list in
a queue sequence and alter its structure considerably.
Both the stack and the queue have been “dead-end” data structures. With the
implementations shown in this chapter you start at one end and you continue to
traverse the linked list until you bump into NULL. Once NULL is reached, the
ride is over and any additional rides require a fresh start from the head of the list.

What if we did away with NULL? Let us try not to point at NULL, but what if
the last pointer in the linked list pointed at the first object? This will create a
circular linked list.

Let us start by looking at a typical queue that was implemented with a linked list.
Node-A is the front node and Node-E is the last node, which points at NULL.
New nodes will be linked to Node-E and de-queueing will happen at the front,
starting with Node-A.

[F] A B C D E

Now we can alter the picture, ever so slightly, by changing one pointer. We can
take the Next pointer, in Node-E, and redirect it to the front. Our drawing now
looks as follows . . . and we have a lovely circular linked list.

Chapter XL Linked Lists II 40.39


[F] A B C D E

It is true that we can change a linear linked list into a circular linked list with a
single program statement, such as P->Next = Front; assuming that P is
pointing at the last node in the linked list. However, such an approach defeats the
purpose of using a circular linked list or ring. A ring should be circular at all
times from the very beginning. Other linked structures have a linking sequence
that is established at the beginning and maintained throughout the existing of the
structure. The same should apply to a ring.

Is there a practical consideration for a circular linked list? Well, the days in our
week behave like a circular queue. We always go from Sunday to Saturday and
then start over again. How about a company that handles maintenance on a
rotating schedule? When maintenance is finished on the last component, it is
time to start back at the beginning. There is a feeling that you start over from the
beginning each time, but the reality behaves more like a circular queue than a
linear structure.

Circular Linked List Definition

A circular linked list is a structure where the nodes are linked in


a continuous, circular sequence without a NULL pointer.

Most circular linked lists are created in a queue sequence,


such that new nodes are linked between the last node and the
first node. Nodes are removed starting with the first node that
was created. Circular linked lists are sometimes called rings.

Now let us check out the implementation of a ring. First, start by recalling a
program that creates a linked list sequenced as a queue. Now comes a question to
mind, what about NULL or perhaps what about identifying the end of the list?
The list may be circular but any traversal needs a starting and ending point and
cannot aimlessly traverse without stopping.

The implementation in program PROG4012.CPP is done with a MyRing class.


This class stores a Start pointer and an End Pointer. Pointer Start will be used
to begin any list traversals and Start will also be the node that is removed if the
list becomes smaller. Our implementation behaves like a circular queue. Pointer

40.40 Exposure C++, Part IV, Abstract Data Types 12-08-99


End is last node in the ring before the traversal repeats itself with the Start node.
This means that End is right next to Start.

When the ring is first started, both Start and End point to the same, single node.
Furthermore, you will observe the odd situation that the Next pointer of the first
node points to itself. Even with a single node the list is indeed circular.

The remainder of the class declaration follows the style of the MyStack and
MyQueue class with a library of functions. The ring will be processed with
functions like ClearRing, EmptyRing, EnRing and DeRing. A constructor
makes sure that the data members are properly initialized and the destructor
returns any allocated memory for future use.

// PROG4012.CPP
// This program demonstrate how to implement a circular
// linked list class. The class is called MyRing.
// The sequence of new nodes are linked in a queue sequence.

#include <iostream.h>
#include <conio.h>
#include "BOOL.H"
// class declaration of a circular linked list
class MyRing
{
public:
MyRing();
// constructor instantiates object with zero elements
~MyRing();
// destructor
void ClearRing();
// makes an existing ring empty
bool EmptyRing() const;
// return true is ring is empty and false otherwise
void EnRing(int RingItem);
// adds RingItem to the ring at the end node
void DeRing(int &RingItem);
// removes a ring element from the start node

private:
struct RingNode
{
int Data;
RingNode * Next;
};
RingNode * Start;
RingNode * End;
bool First;
};

// free function prototypes to test the MyRing class

Chapter XL Linked Lists II 40.41


void FillRing(MyRing &R);
void ShowRing(MyRing &R);

void main()
{
clrscr();
MyRing R;
FillRing(R);
ShowRing(R);
}

/////////////////////////////////////////////////////////////////
// implementations of the free functions

void FillRing(MyRing &R)


{
int Size;
int RingItem;
R.ClearRing();
cout << "Enter ring size ===>> ";
cin >> Size;
cout << endl;
for (RingItem = 1; RingItem <= Size; RingItem++)
{
cout << RingItem << " ";
R.EnRing(RingItem);
}
}

void ShowRing(MyRing &R)


{
cout << endl << endl;
int RingItem;
while (!R.EmptyRing())
{
R.DeRing(RingItem);
cout << RingItem << " ";
}
}

/////////////////////////////////////////////////////////////////
// implementations of the MyRing class member functions

MyRing::MyRing()
{
Start = NULL;
End = NULL;
First = true;
}

MyRing::~MyRing()
{

40.42 Exposure C++, Part IV, Abstract Data Types 12-08-99


RingNode * Temp;
while (Start->Next != End)
{
Temp = Start;
Start = Start->Next;
delete Temp;
}
cout << endl << endl;
cout << "ALLOCATED MEMORY IS RETURNED BY DESTRUCTOR" << endl;
getch();
}

void MyRing::ClearRing()
{
Start = NULL;
End = NULL;
}

bool MyRing::EmptyRing() const


{
return Start == NULL;
}

void MyRing::EnRing(int RingItem)


{
RingNode * Temp;
Temp = new RingNode;
Temp->Data = RingItem;
if (First)
{
Start = Temp;
End = Temp;
First = false;
Start->Next = End;
}
else
{
End->Next = Temp;
End = Temp;
End->Next = Start;
}
}

void MyRing::DeRing(int &RingItem)


{
if (Start != NULL)
{
RingNode * Temp;
Temp = Start;
RingItem = Start->Data;
if (End == Start)
{

Chapter XL Linked Lists II 40.43


Start = NULL;
End = NULL;
}
else
{
Start = Start->Next;
End->Next = Start;
}
delete Temp;
}
}

PROG4012.CPP OUTPUT

Enter ring size ===>> 8

1 2 3 4 5 6 7 8

1 2 3 4 5 6 7 8

ALLOCATED MEMORY IS RETURNED BY DESTRUCTOR

40.10 Linked Lists of Linked Lists

Matrices are frequently used in business, science and engineering. Matrix


Algebra or Linear Algebra is a required course for many majors in college. The
primary purpose of dealing with matrices is to facilitate the solving of equations.
High school students, typically work with equations of one, two or three
unknowns and some math classes may look at four equations with four
unknowns.

In the real (computational) world, matrices with a huge quantity of variables are
frequently necessary. Many equations interact in the design of a car engine, the
lift-off of a space shuttle and the design of a new manufacturing plant.

40.44 Exposure C++, Part IV, Abstract Data Types 12-08-99


Problems that have hundreds of variables often become sparse matrices. This
means that you have a situation where the majority of the spaces in a matrix are
zero. A conventional matrix implementation will create serious memory
problems.

Imagine that it is necessary to store values in a matrix of 1000 rows with 1000
columns. Furthermore, imagine you do not need more than 1%, or 10,000, values
that must be stored. In a conventional two-dimensional array implementation of a
matrix, there would be a terrible waste of memory space. You would reserve
1,000,000 spaces for the array and yet only 10,000 spaces are necessary for the
significant values. Such a situation calls for a different data structure than the
conventional N X N array.

Another problem with this sparse matrix business is that you do not really know
how many values will need to be stored and the anticipation of a large memory
requirement can waste so much useful memory space. What we need is dynamic
memory allocations. We need to use pointers.

This comes as no big surprise in a chapter on pointers, but how does one
implement a two-dimensional, sparse matrix, with pointers? The situation to
consider is a linked list of linked lists. You already know that an array of arrays
creates a two-dimensional data structure. Perhaps the same concept applies to
linked lists.

On the next page we will start by drawing some symbolic pictures to get a feel for
the type of data structure that helps in this situation. Keep in mind that this is not
the only solution for a sparse matrix, but the purpose in this section is to
demonstrate the flexibility of linked lists.

[Front]
RowNr NextRow NextCol ColNr Data NextCol ColNr Data NextCol
24 15 34.6785 54 117.0345

RowNr NextRow NextCol ColNr Data NextCol ColNr Data NextCol


92 105 234.765 389 51.0003

RowNr NextRow NextCol ColNr Data NextCol


237 589 111.1111

RowNr NextRow NextCol ColNr Data NextCol ColNr Data NextCol


734 308 9643.23 735 91.0985

Chapter XL Linked Lists II 40.45


The drawing of the sparse matrix shows two different kinds of pointer nodes.
The first type keeps track of the RowNumber, and provides a link to the next
row with NextRow. This node also behaves like a header node of each individual
linked list with a NextCol pointer. The second type of node holds the
ColNumber and the actual data of the sparse matrix, as well as a NextCol pointer
to the next node in the linked list. You will notice very large gaps in both the row
numbers and the column numbers of this illustration. This is precisely the point
of the sparse matrix and the linked list implementation creates only as many
nodes as are necessary to hold the data. It is true that extra memory is required
for the pointer fields and the row and column numbers need to be stored, but the
total memory usage is far more efficient than a two-dimensional array would
require to accomplish the same job.

Sparse Matrix Case Study


Using the drawing, shown above, as a guide, I will present a case study of a
sparse matrix program to see how a linked list of linked lists is implemented. It
will be interesting to note that there are no new concepts introduced in this
section at all. It is a complex example of the linked data structure business that
has been shown so far. It is also a classic example of drawing the data structure
as you go along to keep track of the nodes and all the pointers. It can get
confusing.

Sparse Matrix Case Study Step #1, Storing Data

Our linked list of linked lists will be implemented with a Sparse class. The first
step of this case study is to identify the structures needed to store the data and the
various pointers that will be used. All this information is placed in the private
segment of the Sparse class.

Each individual node of the linked lists that stores actual data is handled by
ColNode, which has three fields for the ColNumber, the Data to be stored, and a
NextCol pointer. Each header node of the matrix needs to store the
RowNumber, a pointer, NextRow, to the header node of the next linked list and
a pointer, NextCol, to each column node. This job is handled by RowNode.

In this private segment there are also two pieces of information. The pointer that
has access to the header node of the first linked list row, called Head, is stored

40.46 Exposure C++, Part IV, Abstract Data Types 12-08-99


here. There is also a Boolean data member, First, which serves the same purpose
as linked list programs showed in the past.

First is set to true and creates the first two nodes in the data structure. Two
initial nodes may seem odd, but remember that this is a two-dimensional
structure. We need one header node, and then a second node that actually stores
data. One node by itself is not enough. The illustration below shows one
example of getting a sparse matrix started.

[Front]
RowNr NextRow NextCol ColNr Data NextCol
24 54 117.0345

class Sparse
{
private:
struct ColNode
{
int Data; // stores sparse matrix data
int ColNumber; // stores matrix column number
ColNode * NextCol; // pointer to next column node
};
struct RowNode
{
int RowNumber; // stores matrix row number
ColNode *NextCol; // pointer to next column node
RowNode *NextRow; // pointer to header node of next row
};
RowNode *Head;
bool First;
};

Sparse Matrix Case Study Step #2, Member Functions

The second step of our case study looks at the necessary member functions of the
Sparse class. Now do not get confused. This stage is unconcerned how any of
these functions perform their job. Implementation comes later. The only focus
right now is on the actions that need to be performed by the Sparse class. Each
one of the actions will be listed with a brief description of its purpose.

Sparse()
The constructor initializes the Head pointer to NULL and sets the Boolean
variable First to true.

~Sparse()

Chapter XL Linked Lists II 40.47


The destructor does its usual job in a dynamic memory environment by returning
allocated memory when the Sparse object is no longer in scope.

EnterSparse()
This is the function responsible for gathering data from the program user, in this
case entered by the keyboard, and calling the appropriate functions to handle the
new information that is entered.

FirstNode()
If First is true then EnterSparse will call this function, which will create the
very first header node and well as the first column node that stores the first piece
of data entered from the keyboard.

InsertNode()
This is the workhorse function in the group. It is the responsibility of this
function to insure that new nodes are properly inserted in the existing matrix
structure. This job includes not only creating new column nodes for existing
rows, but also to create new header nodes for new rows.

DisplaySparse()
This function traverses the entire matrix structure and displays all the row values,
column values and data values that are stored. This function is considerably more
complex than the standard traversal that you have seen so far.

class Sparse
{

public:

Sparse(); // constructor
~Sparse(); // destructor
void EnterSparse(); // enter sparse values with keyboard
// creates the first header node of the sparse matrix
void FirstNode(int Row, int Col, int Value);
// inserts a new node in a linked list of linked lists
void InsertNode(int Row, int Col, int Value);
// displays all the values stored in the sparse matrix
void DisplaySparse();

private:

struct ColNode

40.48 Exposure C++, Part IV, Abstract Data Types 12-08-99


{
int Data; // stores sparse matrix data
int ColNumber; // stores matrix column number
ColNode *NextCol; // pointer to next column node
};
struct RowNode
{
int RowNumber; // stores matrix row number
ColNode *NextCol; // pointer to next column node
RowNode *NextRow; // pointer to header node of next row
};
RowNode *Head;
bool First;
};

Sparse Matrix Case Study Step #3, Program Output

Since this is a case study you might find it useful to look at a sample output of the
program. The stages shown so far are not close to generating any type of an
execution, but knowing what the goal of the program is will help to understand
the logic of some of the functions that follow. The main function is very simple.
Object S is defined and then EnterSparse and DisplaySparse are called.

void main()
{
Sparse S;
S.EnterSparse();
S.DisplaySparse();
}

The simplicity of the main function hides the real work performed by this
program. Several other functions will do the real work of this program, but these
functions are not called from the main function body.

The sample output execution shows that the matrix is linked in an ordered
sequence in two dimensions. The diagram, shown earlier, illustrated this
symbolically by ordering the row header nodes from top to bottom. The column
nodes were ordered from left to right. The sample output has intentionally
entered sequential numbers, which demonstrates that the output display does not
follow the order that the nodes were created, but the order in which the nodes are
linked in the sparse matrix data structure.

PROG4013.CPP OUTPUT

Chapter XL Linked Lists II 40.49


CALLING SPARSE CONSTRUCTOR

CALLING ENTER SPARSE FUNCTION

Enter [ 0 <space> 0 <space> 0 ] to exit function

Enter Row <space> Col <space> SparseValue ===>> 5 5 1111


Enter Row <space> Col <space> SparseValue ===>> 2 3 2222
Enter Row <space> Col <space> SparseValue ===>> 121 563 3333
Enter Row <space> Col <space> SparseValue ===>> 5 10 4444
Enter Row <space> Col <space> SparseValue ===>> 1 1 5555
Enter Row <space> Col <space> SparseValue ===>> 5 3 6666
Enter Row <space> Col <space> SparseValue ===>> 122 100 7777
Enter Row <space> Col <space> SparseValue ===>> 0 0 0

CALLING DISPLAY SPARSE FUNCTION

[ 1, 1] - 5555
[ 2, 3] - 2222
[ 5, 3] - 6666
[ 5, 5] - 1111
[ 5, 10] - 4444
[121,563] - 3333
[122,100] - 7777

CALLING SPARSE DESTRUCTOR

Sparse Matrix Case Study Step #4, EnterSparse Function

The main function calls two functions, EnterSparse and DisplaySparse.


Function EnterSparse is a typical driver function that determines which other
functions need to be called. You will not see any memory allocation or linking
here, only calls to other functions that perform the linking processes.

void Sparse::EnterSparse()
{
cout << endl << endl;
cout << "CALLING ENTER SPARSE FUNCTION" << endl;
cout << endl;
int Row, Col;
int SparseValue;
bool Done;
cout << "Enter [0 <space> 0 <space> 0] to exit function"
<< endl;

do
{
cout << endl;
cout << "Enter Row <space> Col <space> SparseValue ===>> ";

40.50 Exposure C++, Part IV, Abstract Data Types 12-08-99


cin >> Row >> Col >> SparseValue;
Done = Row == 0;
if (!Done)
{
if (First)
{
FirstNode(Row,Col,SparseValue);
First = false;
}
else
{
InsertNode(Row,Col,SparseValue);
}
}
}
while(!Done);
}

The logic of the EnterSparse function is not at a high cerebral level. The
function consists of a loop that repeats while . . . (!Done); Inside the loop three
values are entered: row index, column index and the sparse matrix value. The
private data member First, checks to see if function FirstNode needs to be
called, and if it does, First is set to false, otherwise function InsertNode is
called. At this level of the sparse matrix program there is no appearance of any
dynamic linked list coding.

Sparse Matrix Case Study Step #5, Constructor

Most classes of any consequence have multiple constructors. Our class will have
to live with one modest default constructor. The constructor may be modest but it
does provide some basic and very important processing that will be performed
automatically every time a new Sparse object is instantiated.

Sparse::Sparse()
{
cout << endl << endl;
cout << "CALLING SPARSE CONSTRUCTOR" << endl;
Head = NULL;
First = true;
getch();
}

Chapter XL Linked Lists II 40.51


As you can see, the constructor is limited. Data member Head is assigned
NULL. Head has the mission to permanently anchor the entry point into the
linked-list-of-linked-lists data structure. Data member First is initialized to true,
which helps to set up the proper first header node. You have learned about this
type of logic when you worked with a modest singly linked list that was linked in
a queue sequence.

Sparse Matrix Case Study Step #6, FirstNode Function

The time has come to start building some type of a linked list. Remember that
there are two types of nodes involved. First there are RowNodes, which act as a
header node for each individual linked list. Additionally the RowNodes store the
RowNumber for the entire linked list and provide a link to the next row, and
header node in the matrix. Second, there are ColNodes, which store the actual
sparse matrix values along with the ColNumber of the stored matrix value.

A diagram is drawn below that shows the minimum size of a linked list of linked
lists, when only one value has been entered. Note that there are two nodes, which
is one RowNode and one ColNode. It will be the job of the FirstNode function
to achieve this minimal, initial, start in creating the sparse matrix structure.

[Head]
RowNr NextRow NextCol ColNr Data NextCol
24 15 34.6785

[Temp]

void Sparse::FirstNode(int Row, int Col, int SparseValue)


{
ColNode * Temp;
Temp = new ColNode;
Temp->ColNumber = Col;
Temp->Data = SparseValue;
Temp->NextCol = NULL;
Head = new RowNode;
Head->RowNumber = Row;
Head->NextCol = Temp;
Head->NextRow = NULL;
}

40.52 Exposure C++, Part IV, Abstract Data Types 12-08-99


The FirstNode function is logically no different from previous, simpler linked
list
arrangements. Two nodes are allocated in memory. Each node is assigned proper
data values. NULL is assigned to terminating pointers and the two nodes are
linked with the NextCol pointer.

Sparse Matrix Case Study Step #7, InsertNode Function

With the construction of these two nodes the “easy” part of this program is now
finished. Now the time has comes to start thinking two-dimensionally. The real
essence of this whole program revolves around writing the InsertNode correctly.
This is not a piece a cake because there are many cases to consider in this process.
You learned about cases before, but that was in a one-dimensional environment.
Now the stakes are higher and along the way you will think back fondly of the
nice, simple linked lists of the previous chapter. All in all, the process is not that
bad. It is really a matter of carefully checking different situations and paying
close attention to details.

Function InsertNode is supplied Row and Col values via parameters to record
the location of the new value to store that is passed to SparseValue. Locally
there are six pointers declared. C1, C2, and C3 will be used with the column
node. R1, R2 and R3 will be used with row node linking.

To help explain this rather long function, text boxes are inserted with
explanations of the code that follows. Work through the function and follow the
logic in a slow step by step manner. It will be particularly helpful if you simulate
an actual linked list and draw each node as it is inserted. Make sure to use a good
distribution of data that will insert nodes in all types to situations. In other words,
check the function for all the different possible cases.

// This function insert a new node in the sparse matrix.


void Sparse::InsertNode(int Row, int Col, int SparseValue)
{
ColNode * C1, * C2, * C3; // creates new column nodes
RowNode * R1, * R2, * R3; // creates new row header nodes

/////////////////////////////////////////////////////////////////
// Space is allocated for a new column node.
// Both SparseValue and ColNumber values are stored in this node.
// The NextCol pointer is assigned NULL.
// This node will be called the Insertion Node for
// identification.
////////////////////////////////////////////////////////////////

Chapter XL Linked Lists II 40.53


C1 = new ColNode;
C1->Data = SparseValue;
C1->ColNumber = Col;
C1->NextCol = NULL;

/////////////////////////////////////////////////////////////////
// The first consideration is to determine if the new node needs
// to be placed ahead of the existing first header node. This
// situation occurs if the Row value is less than the RowNumber
// value of the first header node. If this is true, a new header
// node needs to be created, which will be linked to the
// insertion node and the previous first header node.
/////////////////////////////////////////////////////////////////

if (Row < Head->RowNumber)


{
R1 = new RowNode;
R1->RowNumber = Row;
R1->NextRow = Head;
R1->NextCol = C1;
Head = R1;
}

////////////////////////////////////////////////////////////////
// If the insertion node does not create a new linked list before
// the first header node, a search needs to be performed to first
// identify the proper row location of the insertion node.
// Temporary pointer R2 starts at the Head pointer and traverses
// ”downward” as the Row values is greater than the RowNumber of
// each header node along the traversal. Note that dereferencing
// NULL is avoided by the compound decisions, which will
// ”short circuit” in the event that R2 == NULL.
////////////////////////////////////////////////////////////////

else
{
R2 = Head;
while (R2 != NULL && Row > R2->RowNumber)
// traverse to find proper row location
{
R3 = R2;
R2 = R2->NextRow;
}

/////////////////////////////////////////////////////////////////
// When the loop exits, it is possible that Row is equal to the
// current RowNumber. In that case it is not necessary to create
// a new header node. A check is made to see if this is true by
// comparing Row and the current RowNumber.
/////////////////////////////////////////////////////////////////

if (Row != NULL && Row == R2->RowNumber)

/////////////////////////////////////////////////////////////////

40.54 Exposure C++, Part IV, Abstract Data Types 12-08-99


// It is now established that the current header node is the
// correct linked list for the insertion node. The next step is
// to identify the column position. Pointer C2 is assigned to
// the first column node in the linked list. Before starting any
// traversal, a comparison is made to see if the insertion node
// needs to be linked before the first column node in the current
// linked list. If that is true, the current header node links
// to the insertion node, and the insertion node links to the
// previous first column node.
/////////////////////////////////////////////////////////////////

{
C2 = R2->NextCol;
if (Col < C2->ColNumber)
{
R2->NextCol = C1;
C1->NextCol = C2;
}

/////////////////////////////////////////////////////////////////
// If the insertion node does not link at the head of the current
// linked list, a search must be performed to find the proper
// column position. The C2 pointer traverses with a while loop
// as long as Col is greater than ColNumber. Note that once
// again short-circuiting prevents dereference problems.
// Another pointer, C3 trails C2 one node behind. When C2
stops // there are now two pointers at the location for the
insertion // node. C3 links to the insertion node C1 and the
insertion
// node links to C2.
////////////////////////////////////////////////////////////////

else
{
while (C2 != NULL && Col > C2->ColNumber)
// find proper column insertion position
{
C3 = C2;
C2 = C2->NextCol;
}
C3->NextCol = C1;
C1->NextCol = C2;
}
}

////////////////////////////////////////////////////////////////
// It is easy to think that the job is done. However, there
// exists one more very important case. This is the situation
// of placing the insertion node neither before the first header
// node nor in a linked list with an existing header node. In

Chapter XL Linked Lists II 40.55


// this case a new header node needs to be established to start
// a new linked list. The insertion node will be the first
// column node in the new linked list. After all the previous
// checks and loops, R2 is now at the location in the sparse
// matrix where the new linked list needs to be created
////////////////////////////////////////////////////////////////

else
{
R1 = new RowNode;
R1->RowNumber = Row;
R3->NextRow = R1;
R1->NextRow = R2;
R1->NextCol = C1;
}
}
}

InsertNode Warning

The logic and the code of function InsertNode is quite


complex, especially the first time around.

It will be helpful to take some sample data and draw the


growing linked list of linked lists as it develops.

A drawing is absolutely essential to clarify the complexity


of the various links and conditions that arise.

Sparse Matrix Case Study Step #8, DisplaySparse Function

40.56 Exposure C++, Part IV, Abstract Data Types 12-08-99


After the InsertNode function, life becomes comfortable. The only function left
to describe is the DisplaySparse function. DisplaySparse is not unlike the
function that display the values in a two-dimensional array, which uses nested
loops. Nested loops are used here, but in place of changing index values, there
are now changing NextRow and NextCol pointers that traverse the structure in
two dimensions.

void Sparse::DisplaySparse()
{
cout << endl << endl;
cout << "CALLING DISPLAY SPARSE FUNCTION" << endl;
cout << endl;
int Row, Col;
int Value;
RowNode * R;
ColNode * C;
R = Head;
while (R != NULL)
{
C = R->NextCol;
while (C != NULL)
{
Row = R->RowNumber;
Col = C->ColNumber;
Value = C->Data;
cout << "[" << setw(3) << Row << "," << setw(3) << Col
<< "] - " << setw(5) << Value << endl;
C = C->NextCol;
}
R = R->NextRow;
}
}

Sparse Matrix Case Study Step #9, the Complete Program

In the process of showing one small chunk after another, you may have lost
overlook over the entire program. The final step will show the complete program
and execution in one continuous presentation. Are you ready to write this
program on your own? Could you write additional functions for this program?

Chapter XL Linked Lists II 40.57


APCS Examination Alert

The ability to study a provided program and then alter existing


functions or add new functions is a typical requirement for free
response questions on the AP computer Science Examination.

// PROG4013.CPP
// This program demonstrates one implementation of a "sparse"
// matrix with a linked lists of linked lists.

#include <iostream.h>
#include <conio.h>
#include <iomanip.h>
#include "BOOL.H"

// class declaration of sparse matrix data structure using a


// linked list or linked lists.
class Sparse
{
public:
Sparse();
~Sparse();
// enters sparse values with keyboard entry
void EnterSparse();
// creates the first header node of the sparse matrix
void FirstNode(int Row, int Col, int Value);
// inserts a node in an existing linked list of linked lists
void InsertNode(int Row, int Col, int Value);
// displays all the values stored in the sparse matrix
void DisplaySparse();

private:
struct ColNode
{
int Data; // stores sparse matrix data
int ColNumber; // stores matrix column number
ColNode * NextCol; // pointer to next column node
};
struct RowNode
{
int RowNumber; // stores matrix row number
ColNode * NextCol; // pointer to next column node
RowNode * NextRow; // pointer to next header node
};
RowNode * Head;
bool First;

40.58 Exposure C++, Part IV, Abstract Data Types 12-08-99


};

void main()
{
clrscr();
Sparse S;
S.EnterSparse();
S.DisplaySparse();
}

/////////////////////////////////////////////////////////////////
// implementations of the Sparse class member functions

Sparse::Sparse()
{
cout << endl << endl;
cout << "CALLING SPARSE CONSTRUCTOR" << endl;
Head = NULL;
First = true;
getch();
}

Sparse::~Sparse()
{
cout << endl << endl;
cout << "CALLING SPARSE DESTRUCTOR" << endl;
getch();
}

void Sparse::EnterSparse()
{
cout << endl << endl;
cout << "CALLING ENTER SPARSE FUNCTION" << endl;
cout << endl;
int Row, Col;
int SparseValue;
bool Done;
cout << "Enter [0 <space> 0 <space> 0] to exit function"
<< endl;
do
{
cout << endl;
cout << "Enter Row <space> Col <space> SparseValue ===>> ";
cin >> Row >> Col >> SparseValue;
Done = Row == 0;
if (!Done)
{
if (First)
{
FirstNode(Row,Col,SparseValue);
First = false;

Chapter XL Linked Lists II 40.59


}
else
{
InsertNode(Row,Col,SparseValue);
}
}
}
while (!Done);
}
void Sparse::FirstNode(int Row, int Col, int SparseValue)
{
ColNode * Temp;
Temp = new ColNode;
Temp->ColNumber = Col;
Temp->Data = SparseValue;
Temp->NextCol = NULL;
Head = new RowNode;
Head->RowNumber = Row;
Head->NextCol = Temp;
Head->NextRow = NULL;
}

void Sparse::InsertNode(int Row, int Col, int SparseValue)


{
ColNode * C1, * C2, * C3; // used to create new column nodes
RowNode * R1, * R2, * R3; // used to create new header nodes
// create new node and store sparsevalue
C1 = new ColNode;
C1->Data = SparseValue;
C1->ColNumber = Col;
C1->NextCol = NULL;
if (Row < Head->RowNumber)
// create new first header node if row is less than first row
{
R1 = new RowNode;
R1->RowNumber = Row;
R1->NextRow = Head;
R1->NextCol = C1;
Head = R1;
}
else
{
R2 = Head;
while (R2 != NULL && Row > R2->RowNumber)
// traverse to find row location with trailing pointer
{
R3 = R2;
R2 = R2->NextRow;
}
if (Row != NULL && Row == R2->RowNumber)
// new node is inserted in an existing row
{
C2 = R2->NextCol;
if (Col < C2->ColNumber)

40.60 Exposure C++, Part IV, Abstract Data Types 12-08-99


// new node fits between header and first column node
{
R2->NextCol = C1;
C1->NextCol = C2;
}
else
{
while (C2 != NULL && Col > C2->ColNumber)
// find proper column insertion position
{
C3 = C2;
C2 = C2->NextCol;
}

C3->NextCol = C1;
C1->NextCol = C2;
}
}
else
{
R1 = new RowNode;
R1->RowNumber = Row;
R3->NextRow = R1;
R1->NextRow = R2;
R1->NextCol = C1;
}
}
}

void Sparse::DisplaySparse()
{
cout << endl << endl;
cout << "CALLING DISPLAY SPARSE FUNCTION" << endl;
cout << endl;
int Row, Col;
int Value;
RowNode * R;
ColNode * C;
R = Head;
while (R != NULL)
{
C = R->NextCol;
while (C != NULL)
{
Row = R->RowNumber;
Col = C->ColNumber;
Value = C->Data;
cout << "[" << setw(3) << Row << "," << setw(3) << Col
<< "] - " << setw(5) << Value << endl;
C = C->NextCol;
}
R = R->NextRow;
}
}

Chapter XL Linked Lists II 40.61


PROG4013.CPP OUTPUT

CALLING SPARSE CONSTRUCTOR

CALLING ENTER SPARSE FUNCTION

Enter [ 0 <space> 0 <space> 0 ] to exit function

Enter Row <space> Col <space> SparseValue ===>> 5 5 1111


Enter Row <space> Col <space> SparseValue ===>> 2 3 2222
Enter Row <space> Col <space> SparseValue ===>> 121 563 3333
Enter Row <space> Col <space> SparseValue ===>> 5 10 4444
Enter Row <space> Col <space> SparseValue ===>> 1 1 5555
Enter Row <space> Col <space> SparseValue ===>> 5 3 6666
Enter Row <space> Col <space> SparseValue ===>> 122 100 7777
Enter Row <space> Col <space> SparseValue ===>> 0 0 0

CALLING DISPLAY SPARSE FUNCTION

[ 1, 1] - 5555
[ 2, 3] - 2222
[ 5, 3] - 6666
[ 5, 5] - 1111
[ 5, 10] - 4444
[121,563] - 3333
[122,100] - 7777

CALLING SPARSE DESTRUCTOR

40.11 Practice Exercises

This chapter finishes with a set of exercises in the same manner as the previous
chapter. Once again you will be shown the diagram of some initial linked list. This
is followed by a set of program statements and you select the appearance of the
linked list after the execution of the program statements are completed.

Exercises 1-10 refer to the following declarations:

40.62 Exposure C++, Part IV, Abstract Data Types 12-08-99


struct DblNode
{
char Info;
DblNode * Frwd;
DblNode * Back;
};

DblNode * P1;
DblNode * P2;
DblNode * P3;
char Temp;

Chapter XL Linked Lists II 40.63


01. Consider the following diagram of a partially represented doubly-linked list.
The picture shows Frwd pointing from left-to-right and Back points from right-to-left.

[P1] [P2]

B F K Q W

How is the data structure pictured after the program segment below is executed?

while(P1 != P2 && P1 != NULL)


{
P1 = P1->Frwd;
P2 = P2->Back;
}

[P1] [P2]
(A) B F K Q W

[P1] [P2]
(B) B F K Q W

[P1] [P2]
(C) B F K Q W

[P2] [P1]
(D) B F K Q W

40.64 Exposure C++, Part IV, Abstract Data Types 12-08-99


02. Consider the following diagram of a partially represented doubly-linked list.
The picture shows Frwd pointing from left-to-right and Back points from right-to-left.

[P1] [P2]

B F K Q W

How is the data structure pictured after the program segment below is executed?

while(P1->Info != P2->Info && P2 != NULL)


{
P1 = P1->Frwd;
P2 = P2->Back;
}

[P1] [P2]
(A) B F K Q W

[P1] [P2]
(B) B F K Q W

[P1] [P2]
(C) B F K Q W

[P2] [P1]
(D) B F K Q W

Chapter XL Linked Lists II 40.65


03. Consider the following diagram of a partially represented doubly-linked list.
The picture shows Frwd pointing from left-to-right and Back points from right-to-left.

[P1] [P2]

B F K Q W

How is the data structure pictured after the program segment below is executed?

while(P1->Info <= P2->Info)


{
P1 = P1->Frwd;
P2 = P2->Back;
}
Temp = P1->Frwd->Info;
P1->Frwd->Info = P1->Back->Info;
P1->Back->Info = Temp;

[P1] [P2]
(A) B F K Q W

[P1] [P2]
(B) B Q K F W

[P1] [P2]
(C) B Q K F W

[P1] [P2]
(D) W Q K F B

40.66 Exposure C++, Part IV, Abstract Data Types 12-08-99


04. Consider the following diagram of a partially represented doubly-linked list.
The picture shows Frwd pointing from left-to-right and Back points from right-to-left.

[P1] [P2]

B F K Q W

How is the data structure pictured after the program segment below is executed?

while(P1->Info <= P2->Info)


{
P1 = P1->Frwd;
P2 = P2->Back;
}
Temp = P2->Frwd->Info;
P1->Frwd->Info = P2->Back->Info;
P1->Back->Info = Temp;

[P1] [P2]
(A) B F K Q W

[P1] [P2]
(B) B Q K F W

[P1] [P2]
(C) B Q K F W

[P1] [P2]
(D) W Q K F B

Chapter XL Linked Lists II 40.67


05. Consider the following diagram of a partially represented doubly-linked list.
The picture shows Frwd pointing from left-to-right and Back points from right-to-left.

[P1] [P2]

B F K Q W

How is the data structure pictured after the program segment below is executed?

while(P1->Frwd != NULL && P2 != NULL)


{
P1 = P1->Frwd;
P2 = P2->Back;
}
P1 = P1->Back;
P2 = P2->Fwrd;
Temp = P2->Info;
P2->Info = P1->Info;
P1->Info = Temp;

(A) B F K Q W

(B) W F K Q B

(C) B Q K F W

(D) W Q K F B

40.68 Exposure C++, Part IV, Abstract Data Types 12-08-99


06. Consider the following diagram of a partially represented doubly-linked list.
The picture shows Frwd pointing from left-to-right and Back points from right-to-left.

[P1] [P2]

B F K Q W

How is the data structure pictured after the program segment below is executed?

while(P1->Frwd != NULL && P2 != NULL)


{

Temp = P1->Info;
P1->Info = P2->Info;
P2->Info = Temp;
P1 = P1->Frwd;
P2 = P2->Back;
}

(A) B F K Q W

(B) W F K Q B

(C) B Q K F W

(D) W Q K F B

Chapter XL Linked Lists II 40.69


07. Consider the following diagram of a partially represented doubly-linked list.
The picture shows Frwd pointing from left-to-right and Back points from right-to-left.

[P1] [P2]

B F K Q W

How is the data structure pictured after the program segment below is executed?

while(P1->Frwd != NULL && P2 != NULL)


{
P1 = P1->Frwd;
P2 = P2->Back;
Temp = P1->Info;
P1->Info = P2->Info;
P2->Info = Temp;
}

(A) B F K Q W

(B) W F K Q B

(C) B Q K F W

(D) W Q K F B

40.70 Exposure C++, Part IV, Abstract Data Types 12-08-99


08. Consider the following diagram of a partially represented doubly-linked list.
The picture shows Frwd pointing from left-to-right and Back points from right-to-left.

[P1] [P2]

B F K Q W

How is the data structure pictured after the program segment below is executed?

P3 = new DoubleNode;
P3->Info = 'M';
while(P3->Info > P1->Info)
P1 = P1->Frwd;
P1->Back->Frwd = P3;
P3->Back = P1->Back;
P3->Frwd = P1->Frwd;
P1->Frwd->Back = P3;
delete P1;

(A) B F K M W

(B) B F M Q W

(C) B F K Q M

(D) B M K Q W

Chapter XL Linked Lists II 40.71


09. Consider the following diagram of a partially represented doubly-linked list.
The picture shows Frwd pointing from left-to-right and Back points from right-to-left.

[P1] [P2]

B F K Q W

How is the data structure pictured after the program segment below is executed?

P1 = P2->Back->Back->Frwd
P1->Back->Frwd = P1->Frwd;
P1->Frwd->Back = P1->Back;
delete P1;

(A) B K Q W

(B) B F Q W

(C) B F K W

(D) B F K Q

40.72 Exposure C++, Part IV, Abstract Data Types 12-08-99


10. Consider the following diagram of a partially represented doubly-linked list.
The picture shows Frwd pointing from left-to-right and Back points from right-to-left.

[P1] [P2]

B F K Q W

How is the data structure pictured after the program segment below is executed?

P1->Frwd->Back = Null;
P1->Frwd = Null;
P2->Back->Frwd = P1;
P1->Back = P2->Back;
delete P2;

(A) F K Q W

(B) F K Q B

(C) B F K Q

(D) F K B W

Exercise Answers
1–A 6–D
2–A 7–C
3–B 8–A
4–B 9–C
5–C 10 – B

Chapter XL Linked Lists II 40.73


40.74 Exposure C++, Part IV, Abstract Data Types 12-08-99

You might also like