A linked list is a data structure that
consists of a group of nodes which are linked together. These are
one of the simplest and most common data structures for C programs.
One of the biggest advantages that linked lists have is they are
efficient at inserting and deleting elements within the sequence. The
biggest drawback of linked list is you generally have to traverse the
list to find particular nodes or to get the number of nodes in the
list.
There are several varieties of linked
lists like singly linked, doubly linked and multiply linked. We will
be using singly linked lists in this blog as we create various
networking tools therefore, in this first tutorial, we will build a
singly linked list library that we can integrate with our networking
code. While the code for this library could be optimized and
compacted, I feel that it is more important to make code readable
when learning therefore the code for this library and most of the
code in this blog will be written with functionality and readability
as the primary objectives.
In a singly linked list each node has one or more data fields and
a “next” field that points to the next node in the list. The
preceding image shows how this works where the “next” element
contains a pointer to the next node in the list.
You will be able to see how the linked
list works as we go though the code but first lets take a quick look
at the header file for our linked list library:
#ifndef
LinkedListTest_linkedlistlib_h
#define
LinkedListTest_linkedlistlib_h
struct
node
{
int
num;
char
name[16];
struct
node *next;
};
struct
node *buildnode(int num, char *name);
int
addnode(struct node *head, struct node *add);
int
length(struct node* head);
void
deletelist(struct node **head);
struct
node *searchnode(struct node *head, char *name);
int
removenode(struct node **head, struct node *remove);
#endif
We begin by defining the structure that will hold our data. This
structure can contain whatever data elements you need but you will
need to keep the
struct
node *next
element
because
it points to the next node in the list.
Next
we define six functions that we will be implementing for our library.
These functions are:
- struct
node *buildnode(int num, char *name)
– This is a convenience method that will build the node given the
values pass
in.
This function and
the searchnode()
function are the two functions
that
we will need to change
when we add this library to our projects.
- int
addnode(struct node *head, struct node *add) –
This function will add a node to the end of our list. The
head node
is the first node in our structure while the add
node
is the node to add. This
function will return the number of elements that are in the list
after the new node is added.
- int
length(struct node *head)
– This function will return the number
of nodes in
the list. The head
node is the first node in the list. If we passed another node,
besides the first node of the list, then
this function
would return the number of nodes from that node to
the end of the list.
- void
deletelist(struct node **head)
– Deletes the list and frees the memory being used.
- struct
node *searchnode(struct node *head, char *name) –
This function will search the list and return the first structure
that
has a name
element equal to the
name
argument.
- int
removenode(struct node **head, struct node *remove)
– This function will search the list for the remove
node and remove it from the list. This function will return a 0 if
it was successful in finding
and removing
the node or a -1 if the remove
node could not be found.
Now
lets look at each of the functions and see how they work. We will
start with the struct
node *buildnode(int num, char *name)
function. This function takes
two arguments and build a new node
structure with those arguments.
Here is the code:
struct
node *buildnode(int num, char *name) {
struct
node *new = (struct node *)malloc(sizeof(struct node));
new->num
= num;
strncpy(new->name,
name, sizeof(new->name));
new->next
= NULL;
return
new;
}
This
function starts off by allocating memory needed
for
the
new node
structure with the malloc()
function. We will
need to manually release this
memory
when we are finished
with this node
otherwise we will have memory leaks in our application.
Next
we assign the values to our structure. Notice that we use the
strncpy()
function
to copy the name
value into our structure. This is done because the char pointer that
was passed to our function may get released or reused, so in order to
make sure that we do not lose the
value we copy it
into the structure. We also set the next
pointer to NULL because it is not currently part of the
linked
list. Remember this function just creates a node but does not put it
in a
linked
list. To
put a node into a linked list we
we
use the
addnode()
function.
Now
we will look at our int
addnode(struct node *head, struct node *add)
function. This function will add a node to the end of the
list and
return the total number of nodes after the node is added.
int
addnode(struct node *head, struct node *add) {
struct
node* current = head;
int
count = 0;
while
(current->next != NULL) {
count++;
current
= current->next;
}
current->next
= add;
count++;
return
count;
}
The
addnode()
function
accepts two arguments; the first argument is the first node of
the list and the second argument is the node to add. In order to add
a node to the end of the list, we need to loop though the list to
find the end. This
is a weakness of singly linked list; since
we do not store the end of the list or the number of nodes in the
list we need to loop though the whole list to determine the number of
nodes or to find the end of the list.
Since
we do not want to move the pointer that was passed into the function
which
points to the first node of the list, we create a new pointer that we
name current
that also points to the first node of the list. We then set up a
while loop that loops through the list until we reach the end of the
list. We know that we have found the last node when the next
pointer of that
node points to NULL
rather than another node. If the next
pointer does not point to NULL,
we increment our counter and move the current
point to point to the next node in our list.
Once
we find the last node we set the next
pointer of that node to point to
the node that we are adding to the list.
This will put the new node at the end of the list. This does assume
that the next
pointer within the node that we are adding points
to NULL
and
not another node in the list. If the next
pointer
in the new node points to another node in the list then we would set
up a linked list that loops around in a circle. This would create an
endless loop when we attempt to find the end of the list or
when we search for a specific node in the list.
To be on the safe side we could put a line in the function that sets
the next
pointer of the new node to NULL but I left that out because this
function could also be used to combine two
linked lists where
the next
pointer may point to another node in a separate list.
Next
we will look at the int
length(struct node *head)
function. This function will return the number of nodes in our list.
This function accepts one argument which is
the first node in the list. Here
is the code for the length()
function:
int
length(struct node *head) {
struct
node* current = head;
int
count = 0;
while
(current != NULL) {
count++;
current
= current->next;
}
return
count;
}
The
length function is a duplicate of our addnode()
function
described earlier except that we are not adding a new node to the
list once
we find the end of the list.
Now
lets
look at the void
deletelist(struct node **head)
function.
This
function will delete the whole list and free the memory that the list
uses. This function takes one argument which is a pointer to a
pointer that points the the first
node of the list
to remove. Here is the code for our deletelist()
function.
void
deletelist(struct node **head) {
struct
node *next;
struct
node *current = *head;
while
(current) {
next
= current->next;
free(current);
current=next;
}
*head
= NULL;
}
In
the deletelist()
function we loop through the list. For each node in
the list we
assign the next
pointer to point to the next node in the list. We
then use the free()
function to free the memory that is assigned to the current node.
This
allows your application to reuse this memory location and prevents
memory leaks. We then set the current
pointer to point to the next node in the list. Finally, once all of
the nodes are removed, we set the pointer which pointed to the first
node in the list to NULL
to prevent our application from using it by mistake.
Now
lets see how the struct
node *searchnode(struct node *head, char *name)
function works. This functions searches the linked list for the
first node that has a name
value that is equal to the name
argument passed to the function. This function takes two arguments;
the first argument is a pointer to the first node in the linked list
and the second argument is a pointer to the char string that we are
searching for. Lets look at the code for
the searchnode()
function.
struct
node *searchnode(struct node *head, char *name) {
struct
node* current = head;
while
(current != NULL) {
if
(strcmp(current->name,name) == 0)
return
current;
current
= current->next;
}
return
NULL;
}
We
start off by creating a while loop that loops though our list.
We
then compare the name
element of each node with the name
argument passed into the function using the strcmp()
function. If they are equal, then
we found the node we are looking for and
return the
current node. If they are not equal, we our
loop moves
to the next node. If we are unable to find a node that contains the
name
argument we return NULL.
Finally
lets look at the int
removenode(struct node **head, struct node *remove) function.
This function will remove a specified node from our linked list.
This function takes two arguments; the first argument being a pointer
to a pointer that points to the linked list to search and the second
argument being a pointer to the node to remove. The
removenode()
function
will return 0 if the node was found and removed or -1 if the node was
not removed. Lets
look at the code:
int
removenode(struct node **head, struct node *remove) {
struct
node* current = *head;
struct
node* prev = NULL;
while
(current != NULL) {
if
(current == remove) {
if
(prev == NULL)
*head
= current->next;
else
prev->next
= current->next;
free(current);
return
0;
}
prev=current;
current
= current->next;
}
return
-1;
}
We begin the
removenode()
function
by defining two pointers. We set the first pointer, current,
to point to the first node of our list and set the second pointer,
prev,
to point to NULL.
When we set up our while loop that will loop though our list, the
current
pointer will always point to the current node that we are checking
and the prev
pointer will always point to the previous node. We will need to know
the previous node in our list because when we find the node to
remove, well will need to change the next
pointer of the previous node so it no longer points to the node we
wish to remove. Lets look at a diagram that shows how we are going
to remove a node from our list.
In
the diagram we can see that in order to remove node2 from our list we
need to change the next
pointer of node1 to point to node3 rather than node2. We would then
free the memory allocated to node2 so we do not have memory leaks in
our application.
Once
we have our two pointers defined we create a while loop that will
loop though our list.
If
the current
node is equal to the remove
node then this
is the node to remove. If this is the node to remove we check to see
if it
is the first node in the list by seeing if the prev
pointer is pointing to NULL.
If this is the first node then we change the head
pointer, that was passed into our function, to point to the next node
in the list otherwise we set the next
pointer from the previous structure to point to the node that the
current node's next
pointer is pointing too. Finally we free the memory that is used by
the current node and return 0 signifying
that we found and removed the node.
If
the current
node is not equal to the remove
node then we set the prev
pointer to point to the current
node and the current
pointer to point to the next node in the list. If we loop though the
whole list and we do not find the remove node, then we return a -1
signifying that we could not find the node to remove.
Now
lets look at some code to test our linked list library:
#include
<stdio.h>
#include
"linkedlistlib.h"
#include
<time.h>
void
waitFor(unsigned int secs) {
long
retTime = time(0) + secs;
while
(time(0) < retTime);
}
int
main()
{
printf("Hello
world\n");
int
i;
for
(i=0; i< 500; i++) {
struct
node *first = buildnode(1,"Jon");
struct
node *second = buildnode(2, "Kim");
struct
node *three = buildnode(3, "Kailey");
struct
node *four = buildnode(4, "Kara");
addnode(first,
second);
addnode(first,
three);
addnode(first,
four);
printf("%d)----------\n",i);
printf("Count
%d\n", length(first));
struct
node *found = searchnode(first, "Kailey");
if
(found != NULL)
printf("found:
%d %s\n",found->num, found->name);
removenode(&first,
first);
printf("Count
after remove %d\n", length(first));
removenode(&first,
found);
printf("Count
after second remove %d\n", length(first));
deletelist(&first);
printf("found:
%d\n",length(first));
printf("-------------------\n");
waitFor(2);
}
return
(0);
}
Inside the
main()
function, we begin by creating a for loop that will loop 500 times.
This loop will allow us to see how our library performs by repeating
the various tasks 500 times. If we analyze the memory usage while
this runs it will also help us to detect any memory leaks.
Within the for loop we create four nodes using the
buildnode()
function. We then create our linked list by adding the last three
nodes to our list using the
addnode()
function. Notice that we do not add the first node to the list with
the
addnode() function,
that is because we use that first node as the first node for the
list.
Now that we have our list built we verify that the list contains
four nodes by using the
length()
function which returns the number of nodes in our list and print that
count to the screen.
Next we use the
searchnode()
function to search for a node that contains the
name “Kailey”. If we find a node that contains “Kailey” then
we print the
num
and
name
elements of that structure to the screen.
We then try to remove the first node in our list by calling the
removenode()
function. We verify that the node was removed by printing out the
total number of nodes in the list after we called the
removenode()
function. To verify that we can correctly remove multiple nodes, we
then remove the “Kailey” node that we found earlier in this loop
and once again print out the number of nodes in the list to the
screen.
Finally we delete the list using the
deletelist()
function and once again print out the number of nodes in the list to
verify that they were all removed.
I hope this post helps you understand linked lists a little
better.