A container is a class that holds a collection of objects. All containers have some common properties. Elements can be added and deleted, you can ask how many elements are in the container. Examples include arrays, vectors, lists, stacks and others.
Lists are a container class. Each element in the container includes the location of the next. Elements can be added at either end or anywhere in between. Lists can be empty and have no length limit. You cannot directly access any element as you can in an array. To get to the third element, you have to go through the first two. Access is slower than arrays. Insertions and deletions are easier. Elements can either be single or double linked to each other.
Lists can be implemented in a number of ways. The links between elements can be C++ pointers but can be other things. The definition is that one element must tell where the next is. For example. a set of files, where each file contains the name of the next one in the sequence, would be a list. Our first implementation uses arrays.
We will make an array of structures.
const int SIZE=5;
struct List_data {
Data d;
int next;
};
struct List_data List[SIZE];
Initially, all the next values in the array are set to -1.
When we want to add an element to the list, we look through the array for an element that has a next value of -1. To make the search a little faster, we can keep a counter to tell us where we stopped last.
int Pos = 0;So the process for creating a list is this. We need an integer to hold the location of the beginning of the list.
int lptr=-1;
In our implementation, -1 serves the purpose of NULL. We need an instance of Data and we need to set it. Then we need to find the end of the list. We also need to find an empty element. We copy the Data to that slot. The next field of the element at the end of the list is changed to point to the new element. Now the element is added.
We need the data array initialized so we can use the find_empty() function. Let's set all the next fields in the array to -1.
void
init()
{
for(int i = 0; i < SIZE; i++)
List[i].next = -1;
} // init
The first version of the function uses the Pos variable to record where we last looked. It searched the array circularly looking for slots that aren't used.
int
find_empty()
{
int start = Pos;
// if we are at the end of the array, start at the top
if(start == SIZE) start = 0;
while((start != Pos) && (List[start].next != FREE)) {
start += ++start % SIZE;
} // while
if(List[start].next == -1)
return start;
else
return -1;
} // find_empty
There are several problems that need to be solved in this code. One is that the initial value is wrong. -1 is also used to mark the end of a list. Thus, the last element of list could be mistaken for a free element. We should have used something else to mark free elements. Perhaps something like
const int FREE = -2;
Also notice that we don't change the value of Pos in the code. Let's combine some of these fixes together into e new version.
int
find_empty()
{
int start = Pos;
while(List[start].next != FREE) {
start += ++start % SIZE;
if(start == Pos) return -1; // we wrapped around
} // while
// the only way out of the loop this way is if
// we found one
Pos = (start+1) % SIZE; // start again after the one we found
return start;
} // find_empty
Another variation doesn't use the Pos position to find out if we have wrapped around. We could use a for loop to control how many times we go around.
int
find_empty()
{
int start = Pos;
for(int i = 0; i < SIZE; i++) {
if(List[start].next != FREE) {
Pos = start;
return start;
}
start += ++start % SIZE;
} // for
return -1; // if we get here, we wrapped around
} // find_empty
Let's try one more variation
int
find_empty()
{
for(int start = Pos+1; start != Pos; start += ++start % SIZE;) {
if(List[start].next != FREE) {
Pos = start;
return start;
}
} // for
return -1; // if we get here, we wrapped around
} // find_empty
int
add_element(int L, Data my_d)
{
int lptr = l;
int my_l = find_empty();
if(my_l == -1) return -1; // no memory
List[my_l].d = my_d;
List[my_l].next = -1;
// find the end of the list
while( (l != -1) && (l.next != -1)) {
l = list[l].next;
} // while
if(l == -1)
l = my_l; // first entry
else
List[l].next = my_l;
return 0;
} // add_element
p>
To find an element, we use this algorithm
int
find_element(int l, int one, int two)
{
int lptr = l;
if(l == -1) return -1;
while( (lptr ! = -1) && (list[lptr].d != my_d)) {
} // while
if( lptr == -1) // didn't find it
return -1;
else
return lptr;
} // find_element
This is enough for this implementation. You can see that the pointers are represented by the array references.
We use a similar data structure as above.
struct List_data {
Data d;
List_data *next;
};
Now our list variable is a C Pointer.
In this implementation, we don't need to find a free element, we will use new to make one.
// add an element to the end of the list
int
add_end(List_data* & l, Data my_d)
{
List_data *lptr = l,*my_l;
if( (my_l = new List_data) == NULL) return -1;
my_l->next = NULL; // since this will be the new last element
my_l->d = my_d;
// if there are no nodes on the list, then the new one is
// both the first and last node, so l (remember it is a
// reference parameter) should point at it.
if(lptr == NULL)
l = my_l;
else {
// move lptr until it points at the current last node
while(lptr->next != NULL) lptr = lptr->next;
lptr->next = my_l; // point it at our new node
}
return 0;
} // add_element
// search the list node by node until we find the one
// that contains my_d. Return NULL if we don't find it
List_data *
find_element(List_data *l, Data my_d)
{
List_data * lptr = l;
while(lptr != NULL) {
if( is_equal(lptr->d,my_d) ){
return(lptr); // found it
}
lptr = lptr->next;
} // while
return(NULL);
} // find_element
// add a new node to the front of the list.
int
add_front(List_data * & l, Data my_d)
{
List_data *lp;
if( (lp = new List_data) == NULL) return -1;
lp->d = my_d;
// since this will be the new first one,
// it should point to the current first one
lp->next = l;
l = lp; // make the new one the first one.
return 0;
} // add_front
// search the list for the target data and add a new node after
// that one.
int
insert(List_data* l, Data target,Data my_d)
{
List_data *lp,*lptr;
if((lp = new List_data) == NULL) return -1;
lp->d = my_d;
// use the find_element() function above to locate target
// in the list, if it is there
lptr = find_element(l, target);
if(lptr == NULL) // not there
return(-1);
else {
// the new one should point to the one
// after the one we found (lptr->next)
lp->next = lptr->next;
// the one we found should point to the new one
lptr->next = lp;
// so the new one is after the one we found
}
return 0;
} // insert
// same as above, but takes uses the fact we are adding after the target.
// The one find_element() returns (lptr) can be seen as the front of a list
// that starts with the one that comes after it.
// So we want to add to the front of that list.
// Which is what add_front() does.
int
insert2(List_data* &l, Data target,Data my_d)
{
List_data *lptr;
lptr = find_element(l, target);
if(lptr == NULL)
return(-1); // didn't find it
else
add_front(lptr->next,my_d);
return 0;
} // insert
// walk the list, printing each data element
void
print_list(List_data *l)
{
List_data * lptr = l;
cout << "list: ";
while(lptr != NULL) {
cout << "(" << lptr->d.x << "," << lptr->d.y << ") ";
lptr = lptr->next;
} // while
cout << endl;
} // print_list
Now for something trickier, deletions. This function will remove a given element from the list.
// remove the node that contains the data my_d
int
delete1(List_data *l, Data my_d)
{
List_data *before,*current;
// if the first one is the one we want, delete it quick
if( is_equal(l->d,my_d) ){
l = l->next;
return 0;
}
// set up two pointers. One (current) we walk along the list
// looking for the data my_d.
// the other points to the one that comes before it.
before = l; // we know already that the first one isn't it
current = l->next; // start with the second one
// if we find it, make the one that comes before it
// point to the one that comes after. This deletes the node
// from the list
while(current != NULL) {
if( is_equal(current->d,my_d) ){
before->next = current->next;
}
// move the two pointers to the next nodes
before = current;
current = current->next;
} // while
return -1; // didn't find it
} // delete1
There are some things wrong, find them in class. Here is the complete deletion routine.
// remove the node that contains the data my_d
int
remove(List_data *&l, Data my_d)
{
List_data *before,*current;
if(l == NULL) return(-1);
// if the first one is the one we want, delete it quick
if( is_equal(l->d,my_d) ){
l = l->next;
return 0;
}
// set up two pointers. One (current) we walk along the list
// looking for the data my_d.
// the other points to the one that comes before it.
before = l; // we know already that the first one isn't it
current = l->next; // start with the second one
// if we find it, make the one that comes before it
// point to the one that comes after. This deletes the node
// from the list. Then delete the memory it used.
while(current != NULL) {
if( is_equal(current->d,my_d) ){
before->next = current->next;
delete current;
return(0);
}
// move the two pointers to the next nodes
before = current;
current = current->next;
} // while
return -1; // didn't find it
} // remove
If we assume that l points not to the first element but to a header record, what would the code look like?