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_elementp> 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?