1. 6
6
6
6 L
L
L
LINKED
INKED
INKED
INKED L
L
L
LISTS
ISTS
ISTS
ISTS
A List is an ordered set of items. In computer programs, we often need to maintain lists, such as list of
marks, list of students etc. The most common way to maintain a list is using an array of the list data type.
Eg- int arr[] = {0,1,2,3,4};
char word[] = {‘H’, ’e’, ‘l’, ‘l’, ‘o’, 0};
However, in most programming languages and in C, arrays are of fixed size. Array size must be known at
the compile time, and cannot be changed at run time. However, arrays are allocated continuous memory
blocks, and as each array element is of fixed size, the address of any array element can be calculated
directly. This allows accessing the array elements randomly and efficiently. Time taken to access any array
element is constant time - time taken to calculate the address and memory access time. For example, in
figure 6-1, the address of arr[3] can be directly evaluated as arr + 3* sizeof(array_element).
Figure 6-1 Array organization in memory
6.1
6.1
6.1
6.1 I
I
I
INTRODUCTION TO
NTRODUCTION TO
NTRODUCTION TO
NTRODUCTION TO L
L
L
LINKED
INKED
INKED
INKED L
L
L
LISTS
ISTS
ISTS
ISTS (R
(R
(R
(REFERENCE
EFERENCE
EFERENCE
EFERENCE-
-
-
-B
B
B
BASED
ASED
ASED
ASED L
L
L
LISTS
ISTS
ISTS
ISTS)
)
)
)
To overcome the difficulty of fixed size in static arrays, C and other languages allow one to create Linked
lists that consists of number of nodes linked by their memory addresses. Nodes can be created at run
time and add to the list.
Figure 6-2Illustration of a Linked List
2. Figure 6-2 illustrates a typical linked list with 3 nodes. Each node consists of a data or information part
and a link that carries the address of the next node in the list. In a linked list, nodes need not be created
in continuous memory blocks - this cannot be guaranteed as nodes are created dynamically, and therefore
each node need to be linked to the next node explicitly. A special head pointer is required to keep track
of the linked list.
We can use a C structure to define the composition of a node as follows.
struct node { int info; // assumes int data
struct node* link; };
We can now create a single node linked list as follows.
struct node* head = (struct node*)malloc(sizeof(struct node));
head->info = 1;
head->link = 0;
In the first line, we call sizeof() operator in C for the struct node data type, which returns the number
of bytes, an instance of struct node consumes in memory. This returned value (a long int) is passed in turn
to the malloc(), dynamic memory allocation function in C, usually defined in alloc.h or stdlib.h library.
malloc()allocates the specified number of bytes in memory at runtime and return a pointer to the
allocated memory block. However, to use this memory area as an instance of a struct node, we need to
type cast the return pointer to struct node*. Subsequently, one can access the parts of the struct
node instance as illustrated in the last two lines of the above code segment. The resultant single node list
is illustrated in figure 6-3.
Figure 6-3 Single node Linked List
One can go on adding any number of nodes to the above linked list dynamically at run time. However,
when accessing the nodes, one can only access the head node directly. To access a middle node the list
should be traversed starting from the head node.
Eg - for(temp=head; temp->link; temp=temp->link)
if(temp->info==key) break;
3. Figure 6-4, illustrates variations of singly linked lists - singly linked in the sense there is only one pointer
or link maintained within any node. One can choose a variation of a list in figure 6-4, based on the
application to minimize traversing through the list.
Figure 6-4 Variations of Singly Linked Lists
6.2
6.2
6.2
6.2 A
A
A
ADDING
DDING
DDING
DDING N
N
N
NODES TO
ODES TO
ODES TO
ODES TO S
S
S
SINGLY
INGLY
INGLY
INGLY L
L
L
LINKED
INKED
INKED
INKED L
L
L
LISTS
ISTS
ISTS
ISTS
Nodes can be created at run time, copy the data to be stored in the node, and linked to an existing linked
list. The new node can be added at the head end or tail end of the list or insert after a given node in the
list.
6.2.1
6.2.1
6.2.1
6.2.1 Adding a Node at the Head
Adding a Node at the Head
Adding a Node at the Head
Adding a Node at the Head
Figure 6-5Adding a node at the head end of a Linked List
4. Figure 6-5 illustrates adding a node at the head end of a Linked List. This involves, getting the next
part/variable of the new node point to the current head node and getting the head variable to point to
the newly created node.
Eg- new_node->next = head; // Link the new node at head end
head = new_node; // update head
Another point to consider here is to ensure whether the above two lines of code can cater for any
situation, for example would it work for an empty list. For an empty list, head variable will carry a NULL,
and this NULL value (0) will be assigned to the next field of the new node in the first line of the code above,
thereby properly grounding the new node. This is required as the new node is going to be the tail node as
well, in this scenario. Second line of code, again get the head variable to correctly point to the new node.
6.2.2
6.2.2
6.2.2
6.2.2 Adding a Node
Adding a Node
Adding a Node
Adding a Node at the Tail
at the Tail
at the Tail
at the Tail
Figure 6-6 illustrates, adding a node at the tail end of a Linked List. This involves, getting the current tail
node's next variable to point to the newly created node and properly grounding the new node.
Eg- new_node->next = 0; // Ground the new node
tail->next = new_node; // Link the new node at the tail
tail = new_node; // Update tail if available
If a tail variable is not maintained, then one must get a temporary tail variable pointing to the last node
of the given Linked List.
Eg- for(tail=head; tail; tail=tail->next) if(!tail->next) break;
Once we exit from the above loop, either tail is 0, indicating an empty list or otherwise pointing to the last
node of a given nonempty list.
When we do not maintain a tail pointer, no need to update this variable, as done in the last line of the
previous code, since it is a temporary variable having no further use.
Figure 6-6Adding a node at the tail end of a Linked List
5. As earlier, we need to check whether the previous 3 lines of code can cater for any scenario. To cater for
an empty list as well, we can modify the code as follows.
1 new_node->next = 0; // We still need this
2 if(tail) tail->next = new_node; // if list not empty
3 else head = new_node; // working on an empty list
4 tail = new_node; // Update tail if available as before
Line 1 we need to execute for any scenario as the new node is going to be the last node anyway. For a
nonempty list, tail (given tail variable or a temporary variable set to point to the tail) will be pointing to
the last node, and the new node need to be linked to the last node, as done in Line 2. For an empty List,
however, what we need to adjust is the head variable as this would have been having the value 0, pointing
nowhere. As before, if we maintain a tail variable with the list, then we need to update this variable to
point to the new tail node, as done in Line 4.
6.2.3
6.2.3
6.2.3
6.2.3 Inserting N
Inserting N
Inserting N
Inserting Node
ode
ode
odes
s
s
s
Figure 6-7Inserting a node to a Linked List
AS illustrated in figure 6-7, if we have a pointer temp pointing to a node of a linked list, we can insert a
new node after the node pointed to by temp as follows.
5 newNode->next = temp->next; // Link the nodes after temp to newNode
6 temp->next = newNode; // Link newNode to temp
Again, as before, if we consider possible special scenarios we need to consider cases, (a) temp being
pointing to the tail node, (b) temp pointing to the head node, (c) temp being NULL - inserting to an empty
List.
Case (a): If temp is pointing to the last node, since temp->next is NULL, line 5 will correctly ground the new
node, which is going to be the last node. Line 6 will again correctly link the new node to the previous last
node. The two lines of code will cater for this situation without any change. However, if the list maintains
a tail pointer, then this need to be updated to point to the new node, which has become the tail node of
the list.
6. Case (b): If temp is pointing to the head node, one can easily visualize using the diagram in figure 6-7
that the same two lines of code can function without any changes.
Case(c): if one wants to cater to the scenario of temp being NULL, we can modify the above two lines as
follows.
7 if(temp){ newNode->next = temp->next;
8 temp->next = newNode;
9 } else { temp = newNode;
10 temp->next = 0;
11 }
6.3
6.3
6.3
6.3 D
D
D
DELETING
ELETING
ELETING
ELETING N
N
N
NODES FROM
ODES FROM
ODES FROM
ODES FROM S
S
S
SINGLY
INGLY
INGLY
INGLY L
L
L
LINKED
INKED
INKED
INKED L
L
L
LISTS
ISTS
ISTS
ISTS
Like adding nodes to a linked List, nodes can be deleted from a Linked List in many ways.
6.3.1
6.3.1
6.3.1
6.3.1 Deleting
Deleting
Deleting
Deleting the
the
the
the Head
Head
Head
Head Node
Node
Node
Node
Figure 6-8Deleting a node from the head of a Linked List
As illustrated in figure 6-8, deleting the head node involves, setting the head pointer to the next node
and freeing the head node. The information stored in the head node can be returned if required.
Eg- struct node* temp = head; // get a temp point to head node
head = head->next; // update head to point to new head
free(temp); // free the previous head node
We also need to consider here the special case of deleting the head node from single node Linked list,
resulting an empty list. In this situation head->next will be NULL, and the second line sets head to NULL,
indicating an empty List. The rest functions as normal and no medications are required to the above code
to cater to this scenario.
6.3.2
6.3.2
6.3.2
6.3.2 Deleting the
Deleting the
Deleting the
Deleting the Tail
Tail
Tail
Tail Node
Node
Node
Node
Deleting the tail node is illustrated in figure 6-8. This involves locating the node before the tail node,
grounding it, and releasing the tail node. If the list maintains a tail pointer then that need to be updated
7. to point to the new tail node. As before, if required data stored in the tail node can be returned to the
caller.
Figure 6-9 Deleting the tail node from a Linked List
Eg- for(temp=head; temp->next->next; temp=temp->next);
free(temp->next); // free the tail node
temp->next=0; // ground the node before the tail node
tail=temp; //update tail if maintained
The above for loop locates the node before the tail node. When temp points to the node one before the
tail node, temp->next->next becomes 0 and exit from the loop.
For the above code to work temp->next->next should be valid at least at the loop start. This is valid only
when the linked list has at least two nodes. For this case where we have at least two nodes in the given
Linked List, the above code works correctly. To delete the tail node from a single node Linked List, we can
alter the code as follows.
12 if(!head) return; // cannot delete from an empty list
13 if(head->next){ // have at least 2 nodes
14 for(temp=head; temp->next->next; temp=temp->next);
15 free(temp->next); // free the tail node
16 temp->next=0; // ground the node before tail node
17 tail=temp; //update tail if maintained
18 }else{ free(head); // single node linked list
19 head=tail=0; // setup an empty list
20 }
6.3.3
6.3.3
6.3.3
6.3.3 Deleting a Middle Node
Deleting a Middle Node
Deleting a Middle Node
Deleting a Middle Node
Deleting a middle node from a singly linked list involves, linking the previous node directly to the following
node bypassing the node intended to be deleted and freeing the memory of the node intended to be
deleted. This is illustrated in the figure 6-10.
8. Figure 6-10 Deleting a middle node from a Linked List
To link the previous node directly to the following node, we need to write the next field value of the node
to be deleted (following node's address) to the previous nodes next field. If we have a pointer ptr to the
previous node this is a simple task as illustrated in Case 1 below. However, if we have a pointer to the
node to be deleted then to access the previous node's next field, we need to start from the head and
traverse the list to get a pointer to the previous node, which can be expensive. When we have a pointer
to a node it's easy to delete the following node rather than the node pointed to by the given pointer.
Case 1: Having a pointer ptr, pointing to the previous node of the node to be deleted.
Eg- temp = ptr->next; //get temp point to the node to be deleted
ptr->next = temp->next; // remove the node from the List
free(temp); // Free the node's memory
Case 2: Having a pointer ptr, pointing to the node to be deleted.
Figure 6-11 illustrates an efficient strategy to cater for this situation.
Figure 6-11 Deleting a middle node from a Linked List
9. In figure 6-11, to delete the node pointed to by mid, we copy the data stored in the following node to the
current node, and delete the following node. Copying the following node's data to the current node, we
effectively covert the problem in Case 2 to a problem in Case 1. Deleting the following node is handled as
done by Case 1. The complete code for Case 2 look like follows.
21 mid->data = mid->next->data; // convert to case 1
22 temp = mid->next; // Here onwards identical to case 1 steps
23 mid->next = temp->next; // except ptr is mid here
24 free(temp);
Note that, any of the above solutions cannot be used to delete the tail node. Case 2 can be applied to
delete the head node with an additional statement to update the head variable, whereas Case 1 cannot
apply to delete the head node.
6.4
6.4
6.4
6.4 D
D
D
DOUBLY
OUBLY
OUBLY
OUBLY L
L
L
LINKED
INKED
INKED
INKED L
L
L
LISTS
ISTS
ISTS
ISTS
Figure 6-12 illustrates a Doubly Linked List in contrast to a Singly Linked List. In Doubly Linked List, each
node maintains two pointers, one pointing to the following node as in a Singly Linked List, and a second
pointer pointing to the previous node. This allows a Doubly Linked List to be traversed in both directions.
However, the cost one must incur is the additional pointer that need to be maintained for each node, in
all List operations, such as inserting and deleting nodes.
Figure 6-12 Doubly Linked List vs. Singly Linked List
10. 6.5
6.5
6.5
6.5 L
L
L
LINKED
INKED
INKED
INKED L
L
L
LIST
IST
IST
IST ADT
ADT
ADT
ADT
An example of a singly linked list ADT prototype is given below. Apart from the basic operation discussed
in the previous sections some helper functions are also included to better utilize the ADT. One point of
interest is the use of struct node** type arguments in many functions in the prototype.
Typical Linked List Implementation prototype in C:
struct node{ float data; // data structure for a node
struct node* next;};
struct node* makeNode(float item); //creates a node with data value
// item &returns the node address
struct node* addHead(struct node** list, float item);
// add a new node to the head
struct node* addTail(struct node** list, float item);
// add a new node to the tail
struct node* insert(struct node* ptr, float item); //insert a node
// after the node pointed to by ptr
struct node* delHead(struct node** list, float item); //delete head
struct node* delTail(struct node** list, float item); //delete tail
struct node* del(struct node* ptr, float item); //delete a middle node
void display(struct node* list); // display the list contents
int size(struct node* list); // returns the number of nodes
In the above prototype, a Linked List is identified by a pointer variable pointing to the head node. The type
of this pointer variable should be struct node*. If a function changes the head node of the list, it needs to
alter the value of this pointer variable that identifies the list by holding the address of the head node. As
this pointer variable always lies outside the scope of the function, to write into this pointer variable the
function needs the address (pointer) of this variable. This implies the argument that passes to a function
(that needs altering the head node) should be the address of the pointer variable that points to the head
node. Thus, the type of the function argument receiving the list identity should be struct node**. To
further elaborate this point, let us understand the operation of the addHead() function given in the
prototype implementation above.
addHead() adds a node to the head end of a given Linked List. Given a data value, to create a node, store
the given data value within the node and to return a pointer to the newly created node can be delegated
to a separate function makeNode(). Thus, makeNode() can be reused by other functions intending to add
nodes to the Linked List. addHead() can now create a new node via makeNode() and link the given list to
the new node and assign the head pointer to point to the newly created node as shown in figure 6-5. The
complete implementation code for the addHead() is given below.
11. 30 struct node* addHead(struct node** list, float item){
31 struct node* new_node=makeNode(item); //create node with item
32 if(!new_node) return 0; // return null if makeNode()fails
33 new_node->next = *list; // otherwise link *list to new_node
34 return *list=new_node; // update head to point new_node and
35 } // return *list for any further use
addHead() accepts two arguments, list and item. list is a pointer pointing to a variable that points to the
head node which is maintained, outside the scope of addHead() as shown in figure 6-13. Line 31, calls
makeNode() to create a new node in the heap memory, update data part of the new node with the item
value. Line 32 is an error handling statement to capture possible failure in creating a new node and exit if
necessary. In line 33, *list, which is the value of head is copied to next part of the newly created node, so
that the given linked list trails from the new node. Finally, line 34 updates the head variable with the
address of the new node, and returns the same value to the function name.
Figure 6-13 Usage of addHead() local variables