Linked Lists Linked List ADT A linked list is a series of connected nodes (or links) where each node is a data structure. Dynamically allocated data structures can be linked together to form a chain. A linked list can grow or shrink in size as the program runs.This is possible because the nodes in a linked list are dynamically allocated. Advantages of Linked Lists over Arrays Linked lists are more complex to code and manage than arrays, but they have some distinct advantages. a) A linked list can easily grow and shrink in size The programmer doesn’t need to know how many nodes will be in the list. They are created in memory as needed. b) Speed of insertion or deletion from the list Inserting and deleting elements into and out of arrays requires moving elements of the array. When a node is inserted, or deleted from a linked list, none of the other nodes have to be moved. Composition of a Linked List Each node in the linked list contains - a) One or more members that represent data (e.g. inventory records, customer names, addresses, telephone numbers, etc). b) A pointer, that can point to another node. Data Members Pointer Composition of a Linked List A linked list is called “linked” because each node in the series (i.e. the chain) has a pointer to the next node in the list, e.g. a) The list head is a pointer to the first node in the list. b) Each node in the list points to the next node in the list. c) The last node points to NULL (the usual way to signify the end). pHead struct char Node }; pTail Node { element; *next; *next; struct char Node }; Node { element; *next; *next; struct char Node }; Node { element; *next; *next; NULL Composition of a Linked List The pointers play a big role in maintaining the linked list. The nodes in a linked list can be spread out over memory. Therefore, it is not possible to calculate the address of the next node, like it is possible to calculate the next element of an array. If a pointer from one node to another is lost, the list from that point is lost forever. Therefore, extreme care should be taken when assigning or reassigning a pointer in a linked list. Composition of a Linked List D A E B pHead C If the pointer between any nodes (say B and C) is re-assigned erroneously to null or anything else, the access to the rest of the nodes ( C, D and E ) is then lost. If you loose the head pointer, you loose the entire list. Creating a Linked List Just like any other data type, the information about the node has to be first be declared. Step 1) Declare a data structure for the nodes. struct Node { Object element; Node *next; }; Creating a Linked List a) In this example, the first member of the Node struct is a object called element. It holds the node’s data. This could just as well be just an integer x, or a structure of student records. b) The second member is a pointer called next. It holds the address of any object that is a structure of type Node. Hence each Node struct can point to the next node in the list. The Node struct contains a pointer to an object of the same type as that being declared. It is called a self-referential data structure. This makes it possible to create nodes that point to other nodes of the same type. Creating a Linked List Next, since there is no physical relationship between nodes, a pointer needs to be created to point to the first logical node in the list. Step 2) Declare a pointer to serve as the head of the list: Node *head = NULL; Once you have done these 2 steps (i.e. declared a node data structure, and created a NULL head pointer, you have an empty linked list. head null Declaring a Linked List in a Class class LinkedList{ public: struct Node { Object element; Node *next; Node (Object e, Node* n = NULL ) { element = e, next = n } }; … private: Node* pHead; }; Linked List Operations There are 5 basic linked list operations: – – – – – Appending a node (to the head or to the tail) Traversing a list Inserting a node (into a sorted list) Deleting a node (from the head, tail or middle) Destroying a list Appending a Node When appending a node, there are several things that need to be taken into consideration. For instance: – – – – – Memory has to be allocated for a new node, and the data stored in the memory The insertion point has to be determined – this could be to the head of the list, to the tail of the list or somewhere in the middle Once the insertion point is determined, the new node’s logical predecessor has to be identified Then, the new node has to point to its successor Finally, the predecessor has to point to the new node Appending a Node newNode F D A E B pHead C InsertAfterThis Once the logical predecessor of the new node (InsertAfterThis) is identified, if it is equal to NULL, this signifies that the list is empty or that the insertion is always done to the head of the list. In this instance, the code for inserting the node is similar to: newNode->next = pHead; pHead = newNode; Appending a Node newNode F D A pHead E B C InsertAfterThis InsertAfterThis However, if the logical predecessor of the new node (InsertAfterThis) is identified and it points to a node, this could mean that the node is appended to the middle of the list or to the end. Regardless of where the insertion of the new node is going to be, the code for inserting the node is similar to: newNode->next = InsertAfterThis->next; InsertAfterThis->next = newNode; Appending to the Tail When appending always to the end of the list, here is one algorithm that could be used. a) Create a new node. b) Store data in the new node. c) if there are no nodes in the list Make the new node the first node. else Traverse the List to find the last node. Add the new node to the end of the list. endif. Add a Node to an Empty List New Node h next pHead NULL NULL pHead h next NULL Appending a Node to the end of a List New Node h NULL next pHead e next f next g next NULL f next g next h pHead e next next NULL Appending a Node to the Tail void LinkedList::appendNode(object e) { Node *newNode, *Walker; newNode = new Node; newNode->element = e; newNode->next = NULL; if ( pHead==NULL) pHead = newNode; else // create a new node // if the list is empty add to head { Walker = pHead; while (Walker->next != NULL) Walker = Walker->next; Walker->next = newNode; } // Initialize Walker to head of list // Find the last node in the list // Insert newNode as the last node Appending a Node to the Tail In the previous algorithm, the list had to be traversed all the way to the end to find the last node in the list. One way to make this simpler would be to have a pointer that always pointed to the last node. In this method, traversing the list would then not be required. Appending a Node to the Tail without Traversing the List void LinkedList::appendNode(object e) { Node *newNode; newNode = new Node; newNode->element = e; newNode->next = NULL; if ( pHead==NULL) { pHead = newNode; pTail = newNode; } else { pTail->next = newNode; pTail = newNode; } } // create a new node // if the list is empty add to head // Insert newNode as the last node Appending a Node to the Head When appending always to the head of the list, here is one algorithm that could be used. a) Create a new node. b) Store data in the new node. c) if the list is empty Make the new node the head of the list. else Add the new node to the top of the list Make the new node the head of the list endif. Appending a Node to the Head pHead newNode f g next e next NEXT h next NULL Appending a Node to the Head void LinkedList::appendNode(object e) { Node *newNode; newNode = new Node; // create a new node newNode->element = e; newNode->next = NULL; if ( pHead==NULL) // appending to an empty list { pHead = newNode; } else // appending to an existing list { newNode->next = pHead; pHead = newNode; } } Appending a Node to the Head In the previous algorithm, when appending a node to an existing list, the order in which the statements are written is extremely important. What would happen if the statement were written in reverse order, in this manner? pHead = newNode; newNode->next = pHead; Appending a Node to the Head This would result with the new node pointing to itself, which would make the program go in a never-ending loop when accessing the list. This would also result in loosing access to all the nodes that were currently on the linked list. pHead newNode f g next e next NEXT h next NULL Traversing a Linked List When traversing a list, it is important to remember NOT to move the head pointer from node to node. This would result in loosing access to nodes in the list. Instead, always assign another pointer to the head of the list, and use that pointer to traverse it. Assign list head to walking pointer While walking pointer is not NULL Display the info pointed to by walking pointer Assign walking pointer to its own next member end While Traversing a Linked List void LinkedList::displayList() { Node *Walker; Walker = pHead; while(Walker != NULL) { printInfo(Walker->element) ; Walker = Walker->next; } } Inserting a Node into an Ordered List Create a new node. Store data in the new node. if there are no nodes in the list Make the new node the first node. else Find the first node whose value is greater than or equal the new value, or the end of the list (whichever is first). Insert the new node before the found node, or at end of the list if no node was found. endif Inserting a Node 7 pHead 2 next next 12 newNode 9 null null Inserting a Node nodePtr 7 pHead 2 next next 12 previousNode newNode 9 null null Inserting a Node previousNode 7 pHead 2 next next 12 nodePtr newNode 9 null null Inserting a Node Once the position in which to insert is found, the node needs to be inserted. The order in which the statements are written is important when inserting the node. newNode->next = nodePtr; previousNode->next = newNode; OR newNode->next = previousNode->next; previousNode->next = newNode; Inserting a Node The segment of code from the previous slide will result in the following list: previousNode 7 pHead 2 next next 12 nodePtr 9 newNode next null Inserting a Node However, if the order in which the statements were written were reversed in this manner: previousNode->next = newNode; newNode->next = previousNode->next; This would result with the new node pointing to itself, which would make the program go in a never-ending loop when accessing the list. Inserting a Node The segment of code from the previous slide will result in the following list, where the nodes after the new node are lost, and the list becomes a never ending loop, when traversed. previousNode 7 pHead 2 next next 12 nodePtr 9 newNode next null Inserting a Node into an Ordered List void LinkedList::insertNode(Object e) { Node *newNode, *nodePtr, *previousNode; newNode = new Node; // Allocate a new node & store the new object newNode->element = e; newNode->next = NULL; if (pHead==NULL) // empty list – add to head pHead = newNode; else Walker = pHead; // assign Walker to head and use Walker to find where to insert while ( Walker != NULL && Walker->element.value < newNode->element.value ) { previousNode = Walker; Walker = Walker->next; } if (previousNode == NULL) // new node has the smallest value, insert at head { head = newNode; newNode->next = Walker; } else { previousNode->next = newNode; newNode->next = Walker; } } Deleting a Node Deleting a node is similar to adding a node. The node to be deleted needs to be identified, along with its predecessor. Then, a) Remove the node from the list without breaking the links created by the next pointers. b) Delete the node from memory. It is important to remember to delete the node so that resources are released. Deleting a Node Assume that we need to delete node that contains the value 7. nodePtr 7 pHead 2 next next 12 previousNode The bypassed node is destroyed with the statement delete nodePtr; null Deleting a Node void LinkedList::deleteNode(int num) { ListNode *Walker, *previousNode; if (!pHead) // If the list is empty, do nothing. return; if (pHead->element.value == num) // If it is the first node in the list (special case) { Walker = pHead->next; // set the new head to walker delete pHead; // delete node pHead = Walker; // reassign new head } else { Walker = pHead; // Initialize Walker to head of list while (Walker != NULL && Walker->element.value != num) // skip all nodes whose value is not num { previousNode = Walker; Walker = Walker->next; } previousNode->next = Walker->next; // Link the previous node to the node after Walker delete Walker; // delete Walker } }