Tutorial 14 Eng. Islam Atef Linked lists A linked list is a data structure which is built from structures and pointers. It forms a chain of "nodes" with pointers representing the links of the chain and holding the entire thing together, in which the order of the nodes is determined by the address, called the link, stored in each node. A linked list can be represented by a diagram as shown below: The arrow in each node indicates that the address of the node to which it is pointing is stored in that node. The down arrow in the last node indicates that this link field is NULL. So now we have a list consisting of “nodes” and pointers, the node holds the stored data along with a pointer to the next node in the list. Advantages of linked lists: • Flexible and unbounded size that grows and shrinks as needed Disadvantages of linked lists: • Sequential access only This linked list has four nodes in it, each with a link to the next node in the series. The last node has a link to the special value NULL The NULL pointer shows that it is the last link in the chain. There is also another special pointer, called Start or head, which points to the first link in the chain so that we can keep track of it. We’ll also have another “current” pointer, as we did in linear lists, which represents the last node we looked at. Now let’s look at how to define the linked list in the code. The key part of a linked list is a structure, which holds the data for each node (the name, address, age or whatever for the items in the list), and, most importantly, a pointer to the next node. Here given the structure of a typical node: struct Node { char name[20]; Node *next; }; // Name of up to 20 letters // Pointer to next node The important part of the structure is the line before the closing curly brackets. This gives a pointer to the next node in the list. This is the only case in C++ where you are allowed to refer to a data type (in this case Node) before you have even finished defining it. This is not allowed if next was just a normal variable of type Node, because the compiler won’t know how to allocate its memory because the structures is not even fully defined yet (we’ve mentioned this in the structure section). However, defining a pointer to that type is okay as the pointer only store an address, so it’s memory place is known even before fully defining the struct Node. So to summarize, the linked list consists mainly of the following: • The address of the first node stored in the pointer “head” • Each node which has two components; the info to be stored, and a link to the next node in the list (that stores the address of the next node) • The tail which is the last node Thus far, we’ve defined a structures which represent a single node of the linked list, now let’s define the whole linked list as a class and introduce its members. It’s as shown below: class linked_list { private: struct node; //tells the compiler about the name "node" to be used later until fully defined typedef node* link; //now we define node struct node { elemtype elem; link next; }; link head, tail, current; public : linked_list (); //constructor void insert( const elemtype &e); bool first ( elemtype & e); bool next ( elemtype &e); }; Let’s now look at the implementation of the basic member functions of this class, there could be other member functions (ie: search(), copy(), etc..). Firstly, let’s initialize the class using the constructor to have an empty linked list, as follows: linked_list :: linked_list ( ) { //initialize an empty list head = 0; tail = 0; current = 0; } Moreover, let’s look at the insert() function, as follows: //const call by reference to not change the element sent to be added void linked_list :: insert (const elemtype & e) { // dynamic memory allocation of a new node that'll be added to the list link addnode (new node); // to be sure that a node was allocated, if no space is not available, it returns 0 assert (addnode); // add the value of "e" in the element of the node addnode -> elem = e; // check if the list is empty or not, if empty, make the "head" point to this node if(head ==0) head = addnode; // else, then it's the new tail, so we'll make the old tail point to it else tail->next= addnode; // then make this node the new tail tail = addnode; // with its next pointing to nothing addnode->next=0; } Now let’s see the function first, as shown below: //call by reference to return the first element of the list in e //return type is boolean representing if the first element exists or not bool linked_list :: first (elemtype & e) { // after calling first, current points to first node (head) // if head==0, the list is empty, so we don't have a first element if( head == 0) return false; // else, we have a list consisting of at least 1 element // we'll return the value of the node that "head" points to // and we'll make the current point to this head else { e = head->elem; current = head; return true; } } Lastly, let’s look at the function next, which returns the next element relative to the current value (of the member “current”), as shown below: //call by reference to return the next element of the list in e //return type is boolean representing if the next element exists or not bool linked_list :: next ( elemtype & e) { //for proper use, current should be nonzero. first() must be called at least once assert (current); // after each call, current always points to the item that next has just returned // we want to return the next node to the current node, if the next node is NULL return false if ( current -> next ==0) return false; // if not, shift the current to the next node, and return its element else { current = current -> next; e = current -> elem; return true; } } Now let’s write the main function and see how we’d use such a class to create and print a linked list: int main () { linked_list L ; elemtype x , y; int n,m; cin>>n; //inserting n elements in the list for( int i=0; i<n ; i++) { cin>>x; L.insert(x); } //printing the list elements cout <<" elements of the list "<<endl; //firstly getting the first element in y bool notempty =L.first (y); //as long as we keep finding elements, stay in the loop while (notempty) { //print the first element that we've got cout <<y<<" "; //then get the next element notempty = L.next(y); } return 0; } Doubly Linked lists If we want to add a previous operation to the list by adding a predecessor link to every node. A Doubly Linked List (DLL) contains an extra pointer, typically called previous pointer, together with next pointer and data which are there in singly linked list. This greatly simplifies the insertion and removal operations by simply adding another pointer in the node to the previous node. Let’s look at the class definition for the doubly linked list: class double_list { private: struct Node; typedef Node* link; struct Node { elemtype elem; link next; // Pointer to next node in DLL link prev; // Pointer to previous node in DLL }; link head, current; public: double_list (); //constructor void insert(const elemtype &e); bool first ( elemtype & e); bool next ( elemtype &e); bool previous(elemtype & e); }; Function insert() will be changed as follows, inserting at beginning of list: void double_list:: insert(const elemtype &e) { link add= new Node; assert (add); add->elem=e; add->next = head; if( head) // test to see if list is not empty head->prev= add; add->prev= 0; head=add; } Now let’s implement the previous function, as shown below: bool double_list:: previous(elemtype & e) { assert (current); if( current->prev== 0) return false; else { current= current-> prev; e= current-> elem; return true; } } Problem set 8 (cont.) 5) Write a function that counts the number of the nodes in a linked list. Solution: We can write this function inside the class or outside, we’ll look at both solutions Solution(1): int linked_list::count_node() { if(head==0) return 0; int n=1; current= head; while(current->next !=0) { n++; current=current->next; } return n; } Solution(2): //send the linked list by reference to have the "current" pointer change when calling first() and next() int count_2 (linked_list &A) { elemtype x; int n=0; // get the first node bool found=A.first(x); // if it exists (if the list is not empty basically) if(!found) return 0; // empty list // keep looping as long you find nodes in the list while(found) { n++; found= A.next(x); } return n; } 6) Using the class linked list, what will be the output of the followng program: void linked_list::get( ) int sum =0; current =head; while( current->next != NULL) {sum+= current->elem; current= current ->next;} cout<< sum;} void solve(linked_list &L) { elemtype x; bool found; found = L.first(x) ; if(! found) return; while( found) {cout<<x<<" "; found =L.next(x); found=L.next(x); }} main( ) { linked_list L; elemtype x ; int n; cin>>n; for( int i=0; i<n ; i++) { cin>>x; L. insert (x); } L. get( ); solve(L); } Solution: Assuming this input: 5 7 5 6 3 4 The output is then as follows: 7) To the standard linked list implementaion, add the following functions: 1. append(List A) to append list A at the end of this list(L) 2. List* L.copy() makes a copy of this list L and returns a pointer to it. Solution: void linked_list::append (linked_list A) { // the tail of this link will point to the head of list A // the new tail of this list will be the tail of A tail->next=A.head; tail=A.tail; } linked_list* linked_list::copy() { elemtype y; linked_list* copy_list = new linked_list; bool notempty =first(y); if (!notempty) return NULL; while (notempty) { copy_list->insert(y); notempty = next(y); } return copy_list; } 8) Add a reverse member function to the standard linked list. Implement the reverse operation recursively, using the following observation: If the list is empty, do nothing. To reverse a non-empty list, each node should point to the node that was privously its predecessor, the header should point to the last node, and the node that was formerly first should have a null link. Solution: void linked_list::reverse(link prev=NULL, link next=NULL ,bool first_call=false) { // empty list if (head == NULL) return; // set the current pointer in first call else if (!first_call) { current=head; prev=NULL; next=current->next; first_call=true; } // recursive calling until reaching the last node, then go back recuresively else if (current->next == NULL) { tail = head; head = current; return; } else { prev=current; current = next; next=next->next; } reverse(prev,next,first_call); // going backwards after the recuresive calling current->next=prev; current=prev; } 9) To the standard linked list, add a function Remove to delete any item from the linked list and returns the deleted node to the memory. Then use the class linked list to delete all odd elements(first, third, fifth,...). Solution: bool linked_list::remove(int target_index) { //empty list if (head==NULL) return false; link target=head; link prev=head; //point with "target" to the node with the target index for (int i=1;i<=target_index;i++) { //target index is outside list range if (target->next==NULL) return false; prev=target; target=target->next; } prev->next=target->next; //if the list consists of 1 node only if (target==head && target->next==NULL) { head=NULL; tail=NULL; current=NULL; } // check if your linked list pointers are pointing to the target if (target==current) current=NULL; if (target==head) head=target->next; if (target==tail) { tail=prev; tail->next=NULL; } delete target; return true; } int main() { linked_list L; int n=8; elemtype x[] ={3,4,5,6,8,9,7,1,9}; n=sizeof(x)/sizeof(n); for( int i=0; i<n ; i++) { L.insert(x[i]); } L.print(); elemtype y; bool removed=L.first(y); //if true, we have at least one element in the list for (int i=0; removed; i++) { removed = L.remove(i); } L.print(); return 0; } 10) Write a member function "insertBefore" that will insert an element before the current node of a linked list, make the necessary correction to the list. Solution: void linked_list::insertBefore(const elemtype & e) { //for proper use, current should be nonzero. first() must be called at least once assert (current); link addnode = new node; addnode->elem=e; addnode->next=current; if (head==current) { head = addnode; return; } link prev=head; while (prev->next != current) { prev = prev->next; } prev->next=addnode; } 11) Add the following two member functions to the double linked list: a. function Remove to delete certain element from the double linked list and returns the deleted node to the memory. b. Function Append (list L1 ): to append the list L1 at the end of this list. Solution: bool double_list::remove(int target_index) { //empty list if (head==NULL) return false; link target=head; //point with "target" to the node with the target index for (int i=1;i<=target_index;i++) { //target index is outside list range if (target->next==NULL) return false; target=target->next; } //if the list consists of 1 node only if (target==head && target->next==NULL) { head=NULL; current=NULL; delete target; return true; } // check if your linked list pointers are pointing to the target if (target==current) current=NULL; if (target==head) { head=target->next; target->next->prev=NULL; } else if (target->next==NULL) { target->prev->next=target->next; } else { target->prev->next=target->next; target->next->prev=target->prev; } delete target; return true; } void double_list::append(double_list & L2) { current= head; while(current->next!= 0) current= current->next; current->next=L2.head; L2.head->prev=current; } 12) Modify problem (10) using class doubly linked list. Solution: void double_list::insertBefore(const elemtype & e) { //for proper use, current should be nonzero. first() must be called at least once assert (current); link addnode = new Node; addnode->elem=e; addnode->next=current; addnode->prev=current->prev; current->prev=addnode; if (head==current) { head = addnode; return; } addnode->prev->next=addnode; }