## Presentation on theme: "Linked Lists."— Presentation transcript:

Please Read These slides are provided for the use of students enrolled in James Durbano’s Data Structures class (CISC 220). They are the creation of James Durbano and he reserves all rights as to the slides and their content. These slides are not to be modified or redistributed in any way. All of the slides, including all of their content, may only be used by CISC 220 students for the purpose of reviewing the material covered in lecture. Any other use, including but not limited to, the modification of any slides or the sale of any slides, in whole or in part, is expressly prohibited. Possession of this file implies understanding and agreement to the preceding policy. Further information, including references to outside authors, can be found on the class website.

Objectives Understand the basic building blocks of linked lists.
Understand the advantages (and disadvantages) of using linked lists. Understand the basic insertion and deletion operations associated with linked lists. Be able to code up a linked list and its basic operations.

Linked List Concepts A linked list is a collection of data in which each element contains the location of the next element. Each element in the list contains 2 parts: Data (Contains the stored data) Link (Contains the address of the next element in the list)

There are 4 elements in the list, each one with a data portion and a link portion. pHead is a pointer to the head of the list. Typically, the name given to this pointer is the name of the list. Note the last element of the list. The X in the link portion denotes a NULL pointer (i.e., the end of the list).

A node in a linked list is a structure that has at least 2 fields: one contains the data, the other contains the address of the next element in the list. A node can contain data of any type, including objects of other classes.

Nodes Here we see 3 examples of nodes:
A node with 1 data field (number) and 1 link field. A node with 3 data fields (name, id, grdPts) and 1 link field. A node with 1 data field and 1 link field. However, this data field contains a structure, which itself contains multiple fields.

Nodes The nodes that make up a linked list are self-referential structures. A self-referential structure is one in which each instance of the structure contains a pointer to another instance of the same structural type.

Linked List Concepts Data is stored in a linked list dynamically – each node is created as necessary. Nodes of linked lists are not necessarily stored contiguously in memory (as in an array). Although lists of data can be stored in arrays, linked lists provide several advantages.

A linked list is appropriate when the number of data elements to be stored in the list is unknown. Because linked lists are dynamic, their size can grow or shrink to accommodate the actual number of elements in the list.

Linked List Concepts The size of a “conventional” C++ array, however, cannot be altered, because the array size is fixed at compile time. Also, arrays can become full (i.e., all elements of the array are occupied). A linked list is full only when the computer runs out of memory in which to store nodes. Note: it is possible to create a dynamic array.

Although arrays are easy to implement and use, they can be quite inefficient when sequenced data needs to be inserted or deleted. With arrays, it is more difficult to rearrange data (copying to temporary variables, etc). However, the linked list structure allows us to easily insert and delete items from a list.

Linked List Concepts Unfortunately, linked lists are not without their drawbacks. For example, we can perform efficient searches on arrays (e.g., binary search), but this is not practical with a linked list.

One of the attributes of a linked list is that there is not a physical relationship between the nodes; that is, they are not stored contiguously in memory (as array elements are). To determine the beginning of the list, and each additional element in the list, we need to use pointers.

The pointer to the first node in the list is referred to as the head pointer, because it points to the head node in the list. In addition to the head pointer, there are usually other pointers associated with the list: Pointer to the last element in the list (tail pointer) Pointer that traverses the list to find data (navigator or traversal pointer)

Oftentimes it is convenient to create a structure that contains the head pointer of a list and also information about the list itself (e.g., number of elements currently in the list, maximum number of elements to be allowed in the list, etc). This extra data is referred to as metadata.

A linked list is usually implemented with 2 classes: List class Node class The Node class contains the data and link fields. The List class contains the head pointer and the metadata, along with the list insert and delete functions.

With a linked list, there are a standard set of functions that operate on the list: Creating the list Inserting nodes Deleting nodes Traversing the list Destroying the list

The algorithms we discuss will be implemented using pseudocode. Note that these algorithms are merely used to introduce the concepts involved when working with linked lists … they do not necessarily represent the best way to implement a linked list in practice. The end of the slides contains an actual C++ linked list implementation.

Create List This function is used to initialize a linked list.
Typically, this is implemented in the constructor for the List class.

Create List Pseudocode head = null count = 0 ? \0 ? list count head

Insert Node This function inserts a node into the linked list.
To insert a node into the list, we need a pointer to the node’s logical predecessor. Given the predecessor, there are 3 steps to the insertion: Allocate memory for the new node Point the new node to its successor Point the new node’s predecessor to the new node.

Insert Node The inserted node can come at the beginning, middle, or end of the list. We will look at each of these possibilities …

Insert into an Empty List
When the head pointer of the list is null, the list is empty. To add a node to an empty list, we simply assign the head pointer the address of the new node and set the link field of the new node to NULL to indicate the end of the list. Note: it’s usually a good idea to initialize the link field of a node to NULL in the node’s constructor.

Insert into an Empty List
Pseudocode Here, we want to insert our new node in the list. pNew is a pointer to the new node. head = pNew 1 ? ? \0 \0 75 list count head pNew

Insert at Beginning We insert a node at the beginning of the list anytime we need to insert a node before the first element of the list. To insert at the beginning of the list, we set the link portion of our new node to the head pointer of the list and then set the head pointer to the new node.

Insert at Beginning Pseudocode Here, we want to insert our new
node before 75 to make it the first element of the list. pNew->link = head head = pNew 1 2 ? \0 75 list count head pNew 39 \0

Insert in Middle To insert a node between two nodes, we point the new node to its successor and then point its predecessor to the new node.

Insert in Middle Pseudocode Here, we want to insert our new
node between 39 and 75. pPre is a pointer to the predecessor node. pNew->link = pPre->link pPre->link = pNew 2 3 ? 39 \0 75 list count head pNew 52 \0 pPre

Insert at End To insert a node at the end of the list, we only need to point the last node of the list to the new node and set the new node’s link field to NULL.

Insert at End Pseudocode Here, we want to insert our new
node after 75 at the end of the list. pPre is a pointer to the predecessor node. pPre->link = pNew 4 3 39 75 \0 list count head 52 pPre pNew 134 \0

Insert Node Algorithm if( pPre null )

Insert Node Algorithm Note that although we presented 4 different cases (insert into an empty list, insert at front, insert at back, and insert in middle) we were able to reduce the code to 2 cases: Adding before 1st node or to an empty list. Adding in the middle or at the end of the list.

Delete Node The delete node algorithm logically removes a node from the linked list by changing various link pointers and then reclaiming the memory used by the node. The delete situations parallel those for insert: Delete the only node Delete the first node Delete the last node Delete a node in the middle

Delete First Node When we delete the first node, we must reset the head pointer to point to the first node’s successor and then recycle the memory for the deleted node. The pseudocode to delete the first node in the list also applies to the case where we are deleting the only node in the list.

Delete First Node Pseudocode
Here, we want to delete the first node in the list (39). pPre is NULL as there is no predecessor to 39 and pLoc points to the node we want to delete. head = pLoc->link delete pLoc 3 2 (available) 39 134 \0 list count head 75 \0 pPre pLoc

General Delete Case We call deleting any node other than the first node a general case because the same logic applies to deleting a node in either the middle or at the end of the list. For both of these cases, we simply point the predecessor node to the successor node (of the node being deleted).

General Delete Case Pseudocode Here, we want to delete 75.
pPre is a pointer to the predecessor node and pLoc points to the node we want to delete. pPre->link = pLoc->link delete pLoc 3 2 39 134 \0 list count head 75 (available) pPre pLoc

Delete Node Algorithm if (pPre null) //Deleting first node

Search List A search list function is used by several algorithms to locate data in a list: To insert data, we need to know the logical predecessor to the new data. To delete data, we need to find the node to be deleted and identify its logical predecessor. To retrieve data from a list, we need to search the list and find the data.

Ordered List Search First let us consider searching a list in which the elements are sorted based on a field value. Given a target key, the ordered list search attempts to locate the requested node in the linked list. If a node in the list matches the target, the search returns true, else we return false.

Ordered List Search We start at the beginning and search the list sequentially until the target value is no longer greater than the current node’s key. When the target value becomes less than the current node’s key, we know that the item being searched for cannot be in the list.

Ordered List Search See what happens if target = 3, 4, and 6
pLoc = pHead loop (pLoc not null AND target > pLoc->data.key) pLoc = pLoc->link end loop //Set return value if( pLoc null ) found = false else if(target equal pLoc->data.key) found = true found = false end if return found 1 2 4 5

Unordered List Search In our previous slides, we assumed that the list was ordered on a key. Oftentimes we need to search an unordered list looking for a specific data match. To do this, we use a simple sequential search (i.e., just search every node in order).

Empty List The code we write often assumes that the list is not empty.
Therefore, we will develop a function to check whether or the not the list is empty before we begin searching, inserting, deleting, etc. This is simply done with the following: return (count equal to zero)

Full List Similarly, we must also make sure that our list is not full before we begin. To do this, we need to make sure that our system has more memory and that the number of nodes we have created is less than the maximum number of nodes we have designated for our list.

Full List if (count equal to maxCount) return true end if
allocate pNew if (allocation successful) delete pNew return false

List Count This is a simple member function to return the number of elements in the list: return count

Traverse List Algorithms that traverse a list start at the first node and examine each node in succession until the last node has been processed. Traversal logic is used by several different types of algorithms, such as changing a value in each node, printing the list, summing a field in the list, or calculating the average of a field. Any application that requires that the entire list be processed uses a traversal.

Traverse List To traverse the list we need a walking (navigator) pointer. This pointer is used to move from node to node as each element is processed.

Traverse List pWalker = head loop (pWalker not null) process (pWalker->data) pWalker = pWalker->link end loop We begin by setting the walking pointer to the first node in the list. We then process the first node and continue until all of the data has been processed. When the last node has been processed, the walking pointer becomes null and the loop terminates.

Destroy List When a list is no longer needed but the application is not finished, the list should be destroyed. This function will delete any nodes in the list, reclaim their memory, and set any metadata to an empty list condition. This type of functionality is commonly found in the destructor.

Testing As with all software development applications, it is vital to test your implementation. Here are some tests to run on your toolbox of linked list functions to verify correct operation.

Testing Insert Logic Insert a node into an empty list.
Insert a node before the 1st node. Insert a node between two nodes. Insert a node after the last node.

Testing Delete Logic Delete the entire list. Delete the first node.
Delete a node between two nodes. Delete the last node. Try to delete a node that doesn’t exist. Try to delete a node whose key is less than the first data node’s key. Try to delete a node whose key is greater than the last data node’s key. Try to delete from an empty list.

Circular Linked Lists In a circular linked list, the last node’s link points to the first node of the list.

Circular Linked Lists Insertions and deletions into a circular linked list follow the same logic patterns used in a singly linked list except that the last node points to the first node. Therefore, when inserting or deleting the last node, in addition to updating the tail pointer, we must also point the link field of the new last node to the first node.

Circular Linked Lists Note that the last element in the list does not have a NULL valued link field … it points back to the first node. In this case, how do we know when we have finished searching the list? loop (target not equal to pLoc->data.key AND pLoc->link not equal to head) Note: you must still account for the last node if you implement this logic.

Circular Linked Lists One application: a queue. When we get to queues next week, we will see that they are essentially a linked list with pointers to the head and tail nodes. However, if the queue is implemented using a circular linked list, only one pointer is needed -- tail. Why? because head = tail->next …

Doubly Linked Lists One of the most powerful variations of a linked list is the doubly linked list. A doubly linked list is a linked list in which each node has a pointer to both its successor and its predecessor.

Doubly Linked Lists The doubly linked list eliminates the need for the “pPre” pointer since each node has pointers to both the previous and next nodes in the list.

Doubly Linked Lists A variation on the doubly linked list is the circular doubly linked list. In this variation, the forward pointer of the last node points to the first node and the backward pointer of the first node points to the last node. Advantage: if you have a “dummy” node at the beginning of the list, there are no special cases to worry about when inserting/deleting.

Multilinked Lists A multilinked list is a list with two or more logical key sequences. For example, consider the list of the first 10 presidents of the United States. We could have a list that is sorted by: Date the president assumed office Alphabetical order based on the president’s last name Alphabetical order by the president’s wife’s maiden name etc.

Multilinked Lists We may wish to have the list sorted in all these formats. The beauty of the multilinked list is that the data exists only once, but there are different pointers for the different lists. Last Slide of First Lecture

Actual Implementations
Up to now, we have introduced everything in very abstract terms and have used pseudocode. Now we will develop a more concrete view of the linked list. In the coming slides, we will develop the C++ code to implement a basic linked list and some of the functions associated with it. Beginning of Second Lecture

Introduction to the Code
We will have 2 main classes: List Node List is the class that all the functions of the list will be included in (e.g., add, delete, print, etc). Node is the class that will contain the structure and operations for a specific node in the linked list.

Node Class class Node { friend class List; public: Node(); Node(int);
We declare List as a friend of Node. This way, List will be able to access Node’s private data members directly. class Node { friend class List; public: Node(); Node(int); private: int data; Node *next; }; Here are our constructors: the first one is the default constructor. data is the value stored in the node while next is the link pointer – the pointer to the next node in the list.

Node Class Constructors
Node::Node() { data = 0; next = NULL; } Node::Node(int value) { data = value; The constructors initialize the data value and also set the link field of the node to NULL. This constructor assigns a given data value upon instantiation.

List Class Functions to print and delete the list. A helper function.
class List { public: List(); void printList(); void deleteList(); bool isEmpty(); Node* findNode(int); void addToFront(Node *); void addToBack(Node *); void deleteFromFront(); void deleteFromBack(); private: int numNodes; Node *head; }; A helper function. A function to find a node with a given value. Functions to add/delete nodes to/from the list. Metadata (number of nodes in the list) and a pointer to the first node in the list.

List Class Constructor
Here we initialize the number of nodes in the linked list to 0 and set the head pointer to NULL. Remember: the constructor is called upon instantiation. Since the list was just instantiated, it has no nodes yet. List::List() { numNodes = 0; head = NULL; }

deleteList void List::deleteList() { Node *toDelete;
This function is used to delete the entire list. deleteList void List::deleteList() { Node *toDelete; while( head != NULL ) { toDelete = head; head = head->next; delete toDelete; } numNodes = 0; We go through the list, node by node, deleting each node along the way. Since the list is now empty, we set the number of elements in the list to 0.

isEmpty bool List::isEmpty() { if (numNodes == 0) { return true; }
If the list is empty, this function will return TRUE. If there are nodes in the list, it will return FALSE. isEmpty bool List::isEmpty() { if (numNodes == 0) { return true; } else { return false; Here we check to see if the list is empty … what’s another way to check to see if the list is empty? if (head == NULL) …

This function is used to insert a node at the front of the list. addToFront void List::addToFront(Node *nodeToAdd) { nodeToAdd->next = head; head = nodeToAdd; numNodes++; } We have to make sure to update the head pointer. We increment the number of nodes in the list when we’re done.

This function is used to insert a node at the back of the list. addToBack 1 2 4 5 void List::addToBack(Node *nodeToAdd) { if( isEmpty() ) { head = nodeToAdd; } else { Node *navigator = head; while( navigator->next != NULL ) { navigator = navigator->next; navigator->next = nodeToAdd; numNodes++;} If the list is empty, we need to handle the insert differently. We are traversing the list to find the last node. We increment the number of nodes in the list when we’re done.

deleteFromFront void List::deleteFromFront() { if( !isEmpty() ) {
This function is used to delete a node from the front of the list. deleteFromFront void List::deleteFromFront() { if( !isEmpty() ) { Node *newHead = head->next; delete head; head = newHead; numNodes--; } else {} We have to make sure to update the head pointer. We decrement the number of nodes in the list when we’re done.

This function is used to delete a node from the back of the list.
deleteFromBack void List::deleteFromBack() { if( !isEmpty() ) { Node *follower = NULL; Node *leader = head; if( numNodes == 1 ) { delete head; head = NULL; } else { while( leader->next != NULL ) { follower = leader; leader = leader->next; delete leader; follower->next = NULL; numNodes--; }} Note that we need 2 temp pointers for this function. We need to handle the special case of a list with 1 node. We decrement the number of nodes in the list when we’re done.

This function returns a pointer to the node with a given value.
findNode Node* List::findNode(int valueToFind) { Node *navigator = head; while(navigator != NULL && navigator->data !=valueToFind){ navigator = navigator->next; } return navigator; If this is not the 1st condition in the loop, your program might seg fault. Keep looping until we find the node with the data.

printList This function prints the entire list.
void List::printList() { cout << "Number of elements in list: " << numNodes << endl; Node *navigator = head; while( navigator != NULL ) { cout << (navigator->data) << endl; navigator = navigator->next; } Initially we print the number of elements in the list. Then we traverse the list, printing out the data values as we go.

main Node *newFirstNode = new Node(2);
list.addToFront(newFirstNode); list.printList(); list.deleteFromBack(); list.deleteFromFront(); list.deleteList(); return 0; } int main(int argc, char* argv[]) { List list; list.printList(); Node *firstNode = new Node(5); list.addToFront(firstNode); Node *lastNode = new Node; list.addToBack(lastNode); Note that when we make a new node, it is declared as a pointer to a node.

Output of Program

Another Example In this example, we will develop the C++ code that reads a list of integers from the keyboard, creates a linked list out of them, and prints the results.

Node Class class Node { friend class List; public: Node(int); private:
We declare List as a friend of Node. This way, List will be able to access Node’s private data members directly. class Node { friend class List; public: Node(int); private: int data; Node *next; }; We make sure to create a constructor. data is the value stored in the node while next is the link pointer – the pointer to the next node in the list.

List Class class List { public: List(); void addToList(Node *);
void printList(); private: Node *head; }; The List class is what contains all the functions we use to operate on the list. head is a pointer to the first node of the list.

Constructors List::List() { head = NULL; } Node::Node(int value) {
The List constructor initializes the empty list. List::List() { head = NULL; } Node::Node(int value) { data = value; next = NULL; The Node constructor initializes the data to the given value and the link pointer to NULL.

head = nodeToAdd; } else { Node *navigator = head; while( navigator->next != NULL ) { navigator = navigator->next; navigator->next = nodeToAdd; We handle the special case of an empty list. Here, we traverse the list until we get to the last node. Once we get to the end of the list, we insert the node.

printList void List::printList() { Node *navigator = head;
Here, we traverse the list and print out each node’s data along the way. void List::printList() { Node *navigator = head; cout << "List: "; while( navigator != NULL ) { cout << navigator->data << " "; navigator = navigator->next; } cout << endl;

Main int main(int argc, char* argv[]) { List list; int input, done = 0; while( !done ) { cout << "Enter an integer (-9999 to quit): "; cin >> input; if( input != ) { Node *newNode = new Node(input); list.addToList(newNode); } else { done = 1; } list.printList(); Here we read a list of integers from the user until we receive a –9999. After each integer, we add the node to the list.

DRAW A PICTURE DRAW A PICTURE DRAW A PICTURE Final Suggestion
To code linked lists, there is only one thing to remember … DRAW A PICTURE DRAW A PICTURE DRAW A PICTURE