L04 - Linked List - II - With - Notes
L04 - Linked List - II - With - Notes
Linked Lists II
College of Engineering
School of Computer Science and Engineering
1
22 February 2020
TODAY
• sizeList() function
Today we will learn about LinkedList C struct. This is the core of today’s
lecture.
2
22 February 2020
LEARNING OBJECTIVES
3
22 February 2020
TODAY
• sizeList() function
4
22 February 2020
PREVIOUSLY
Earlier we learned about four main functions. The printList function prints every item
that is stored in a linked list. The findNode function takes the index value and returns
you a pointer to the node you are looking for. The insertNode function allows you to
add a new value to anywhere in the linked list. You can try the RemoveNode function
in the lab.
Now, in order to modify the address stored in the head pointer, we need to pass a
pointer to the head pointer. This is ListNode ** pointer. This makes things
complicated, and when you write the code, you have to think whether you should
dereference it once or twice, or whether it is a direct pointer, etc. Therefore, we need
to make things clear.
5
22 February 2020
sizeList() FUNCTION
The sizeList function is really simple. You have to return the number of nodes in the
linked list. Use the head pointer and keep following the next pointer until you hit the
next that equals to NULL. Every time you move one step down, you should increment
the counter by 1. This is very similar to the recursive function on counting digits.
6
22 February 2020
sizeList() [VERSION 1]
This is the code for the sizeList function. Note that in the code, head is a local variable
which initially holds the value of the head pointer, that is the address of the first
node. We can use this as a temporary variable. You will see, shortly, the cases that do
not allow you to do this.
7
22 February 2020
TODAY
• sizeList() function
8
22 February 2020
In this application, I want to add values to the linked list until I have a certain number
of values inside. We will start inserting random values from the beginning of the
linked list. So, we have a while loop which runs while the sizeList(head) is less than
10. I pass in the head pointer, and every time I go through this function and I decide
whether to continue, by checking the number of nodes inside the linked list. So, as
long as I don't have a certain number of nodes in my list, I will keep inserting a new
number.
9
22 February 2020
1 int main(){
2
3 ListNode *head = NULL;
4
5 srand(time(NULL));
6 while (sizeList(head) < 10){
7 insertNode(&head, 0, rand() % 100);
8 printf("List: ");
9 printList(head);
10 printf("\n");
11 }
12 printf("%d nodes\n", sizeList(head));
13
14 while (sizeList(head) > 0){
15 removeNode(&head, sizeList(head)-1);
16 printf("List: ");
17 printList(head);
18 printf("\n");
19 }
20 printf("%d nodes\n", sizeList(head));
21
22 return 0;
23 }
In this application, I want to add values to the linked list until I have a certain number
of values inside. We will start inserting random values from the beginning of the
linked list. So, we have a while loop which runs while the sizeList(head) is less than
10. I pass in the head pointer, and every time I go through this function and I decide
whether to continue, by checking the number of nodes inside the linked list. So, as
long as I don't have a certain number of nodes in my list, I will keep inserting a new
number.
10
22 February 2020
Now, what I want you to think about is how many times does thr size list function get
called? Here, we call sizeList function five times. Every time I call sizeList function, it
traverses all the way down and increments the counter to figure out how many nodes
there are in the linked list. But it is a waste of time to traverse the entire linked list
many times.
11
22 February 2020
1 int main(){
2
3 ListNode *head = NULL;
4
5 srand(time(NULL));
6 while (sizeList(head) < 10){
7 insertNode(&head, 0, rand() % 100);
8 printf("List: ");
9 printList(head);
10 printf("\n");
11 }
12 printf("%d nodes\n", sizeList(head));
13
14 while (sizeList(head) > 0){
15 removeNode(&head, sizeList(head)-1);
16 printf("List: ");
17 printList(head);
18 printf("\n");
19 }
20 printf("%d nodes\n", sizeList(head));
21
22 return 0;
23 }
Now, what I want you to think about is how many times does thr size list function get
called? Here, we call sizeList function five times. Every time I call sizeList function, it
traverses all the way down and increments the counter to figure out how many nodes
there are in the linked list. But it is a waste of time to traverse the entire linked list
many times.
12
22 February 2020
• Very inefficient!
• How often does the number of nodes change?
- Only when you do the following
• Add a node
• Remove a node
- So why recalculate every single time?
When we add a node, the size of the list goes up by 1. When we remove a node, it
goes down by 1. When I print a list or run findNode function, the size of the list won’t
change. Therefore, we do not want to bother about the size of the linked list each
time. I create a separate variable, ‘’listsize”, that will keep track of the size of the
linked list. Every time we insert a node, the value of the listsize will increase by 1, and
it will decrease by 1 every time we remove a node. The starting value of the listsize is
0 as we start with an empty list.
13
22 February 2020
Previously, we called sizeList function several times. But we will now update the
listsize variable instead.
Although this is more efficient than the previous version of the code, it is not good for
you as a programmer because now you have to keep track of both the head pointer
and the listsize variable. You have to make sure that the listsize variable is updated
every time you insert or remove a node.
If you have an array of linked lists, you should have an array of listsize values, and
make sure that you access the correct one when needed. Now, this is also inefficient.
One of the reasons why we learned C struct is that we need to wrap up the things
that are related. Therefore, we will apply the same thing here.
14
22 February 2020
1 int main(){
2 ListNode *head = NULL;
3 int listsize = 0;
4 srand(time(NULL));
5 while (listsize < 10){
6 insertNode(&head, 0, rand() % 100);
7 listsize++;
8 printf("List: ");
9 printList(head);
10 printf("\n");
11 }
12 printf("%d nodes\n", listsize);
13
14 while (size > 0){
15 removeNode(&head, listsize-1);
16 listsize--;
17 printf("List: ");
18 printList(head);
19 printf("\n");
20 }
21 printf("%d nodes\n", listsize);
22
23 return 0;
24 }
Previously, we called sizeList function several times. But we will now update the
listsize variable instead.
Although this is more efficient than the previous version of the code, it is not good for
you as a programmer because now you have to keep track of both the head pointer
and the listsize variable. You have to make sure that the listsize variable is updated
every time you insert or remove a node.
If you have an array of linked lists, you should have an array of listsize values, and
make sure that you access the correct one when needed. Now, this is also inefficient.
One of the reasons why we learned C struct is that we need to wrap up the things
that are related. Therefore, we will apply the same thing here.
15
22 February 2020
TODAY
• sizeList() function
Previously, we had list node structs. Each piece of information in our list gets its
own struct. Now we will wrap up the entire list into one struct.
16
22 February 2020
Currently, we have the head pointer and the listsize that are related to our linked list.
So, there are multiple things to take care of. Now, if I write a function which
separately adds a node and removes a node, I have a rely on that function to
increment or decrement the listsize variable correctly. Most importantly, I need to
pass the listsize variable into that function to get access to the variable. This makes
the parameter list longer. Also, if you have a function that modifies the linked list
structure, you need to have a pointer to the head pointer which is complex.
17
22 February 2020
Linkedlist C STRUCT
• Solution
- Define another C struct, LinkedList
- Wrap up (encapsulate) all elements that are required to
implement the linked list data structure
Now, I have wrapped both the head pointer and the listsize variable into a separate
structure. This is a linked list structure. If you look at the image, the actual list node
structs are not drawn inside the linked list structure because the linked list struct has
only two components. The head pointer points to the actual list node structs, but
since structs are not inside the linked list structure, you have to be careful.
By looking at the new versions of the functions that we have learned previously, we
can see how this linked list structure is important.
18
22 February 2020
In the original function prototypes, you pass in the head pointer for printList and pass
in the head pointer for findNode. To insert and remove a node, you pass in the
pointer to the head pointer. In the new version of the function prototypes, instead of
the head pointer, we pass in the linked list struct. We need to pass it in by reference
because we want the function to be able to modify the actual linked list object. Now
we do not want to worry about different parameter list. We have to pass in the linked
list struct regardless of the function.
Thus, don't need to worry about double pointer with linked list II
19
22 February 2020
1 int main(){
2
3 LinkedList ll;
4 LinkedList *ptr_ll;
5
6 insertNode(&ll, 0, 100);
7 printList(&ll);
8 printf(“%d nodes\n”, ll.size);
9 removeNode(&ll, 0);
10
11 ptr_ll = malloc(sizeof(LinkedList));
12 insertNode(ptr_ll, 0, 100);
13 printList(ptr_ll);
14 printf(“%d nodes\n”, ptr_ll->size);
15 removeNode(ptr_ll, 0);
16 }
Now let’s see how we use the linked list struct with both dynamic and static memory
allocation. In here, ll is a proper linked list struct which has two fields and takes up a
certain amount of memory. This is a statically declared linked list struct.
In line 4, we declared pointer ll, a linked list object from the heap using malloc. From
line 6 to 9, we use the statically declared linked list struct. We pass in a pointer to the
C structure so we can print, insert and remove nodes. But, since we have a linked list
struct now, we can also use the malloc to create a linked list structure dynamically.
Now, I am going to call malloc and get enough memory to hold the linked list struct.
This linked list struct keeps track of the list nodes and size of the linked list. If you are
dealing with a struct itself, the notation is a dot. But if you are accessing a struct using
a pointer, then you should use the arrow notation to get inside the variables.
In line 11, we ask for enough space to hold the linked list struct. I assigned that
memory location to pointer ll. And subsequently, I will perform the same functions as
inset, print the list, print the size of the list and remove.
20
22 February 2020
temp 0×100
Previously, we passed in a head pointer for printList function, but now we are passing
in a linked list struct. In previous parameter list, we used the head as a local pointer
variable which gets a copy of the value outside from the head pointer.
If I use II-> head as the temporary pointer, I am destroying the structure of the linked
list itself because I am keep changing the value of the pointer to go down the linked
list.
So I need to create the *temp as a temporary pointer just for the function. Now we
can use this local variable as a temp pointer. We can use it to go down the list nodes.
The outside head pointer points at the actual first node even though we use the temp
pointer to move down the list.
21
22 February 2020
1 ListNode * findNode(
2 LinkedList *ll, int index){
3 ListNode *temp = ll->head; ListNode *head
4 if (temp == NULL || index < 0)
5 return NULL; 0×100 10 20
6 2
7 while (index > 0){
temp = temp->next; int size
8
9 if (temp == NULL)
10 return NULL;
11 index--;
12 }
13 return temp; temp 0×100
14 }
In findNode function, we cannot use the head pointer as the temporary pointer.
Therefore, I have declared listNode * temp. The rest of the function is similar to the
previous version.
22
22 February 2020
ListNode *head
10
0x100 10 20
int listsize 2
int size
In the old method of inserting node, we have added the listsize variable to keep track
of the size of the linked list.
Now, what if I make a mistake when updating the value of the listsize, or if someone
else uses the insert node function and modify your linked list instead?
To take care of such a problem, we need to pass in the listsize variable to the insert
node function as a parameter.
That is the old method. In the new version, instead of worrying about dereferencing
the correct things, we just need to pass in the single list node pointer ll. Once we
have access to the linked list struct, we have access to the head pointer and the list
size variable.
By doing that, you do not need to change the parameter list for the insert node or
remove node functions.
23
22 February 2020
ListNode *head
10
0×100 10 20
int listsize 2
int size
Now you can rewrite the functions with the linked list struct.
I will provide you with the reference code in a week. You can compare it with my
code after trying it out yourself.
24
22 February 2020
Linkedlist STRUCT
0×100 10 20
2
int size
The reason why we have these structures in C is that each object represents a certain
concept.
Likewise, now the linked list struct allows us to treat the entire linked list as an object,
allowing us to easily create an array of the linked list. We can easily pass in the linked
list struct to other functions with a pointer to the linked list struct.
25
22 February 2020
Now we can rewrite the sizeList function. The size variable is a part of the
LinkedList struct. The size of the linked list is the value of the size variable. If we
want to write a function for it, it returns ll->size.
Now, every time I call insert node function, it will automatically increase the value of
the size variable and decrease it when I call remove node function. By having an
additional variable to store the size of the linked list, we will only use 4 bytes from
memory. Is that important?
The expected size of a linked list is a lot more than 4 bytes, and therefore, 4bytes is
trivial. But, if we are a running a system with only 16 bytes of memory, 4 bytes can
make a big difference. And, in such a situation, traversing the linked list to calculate
the size of the list every time is better.
So, you have to know that sometimes it makes more sense to use memory to pre-
store a value and sometimes it is better to recalculate it whenever it is needed.
26
22 February 2020
TODAY
• sizeList() function
Let’s spend about five minutes talking about more complex linked list before we
end off with a summary of what's good and bad about linked list and when you
will use them.
27
22 February 2020
So far we've talked about singly linked lists. In singly linked lists, we store one integer
value inside each node, and we have only one next pointer for each node. This
means, when traversing a singly linked list, we can only go one way, starting from the
first node using the head pointer and go all the way down of the linked list.
We cannot start traversing the linked list from the last node and traverse to the first
node of the linked list.
If we want to traverse backwards as well, we need to connect each of the linked list
nodes to the node after and the node before.
28
22 February 2020
• Inserting a node
- Have to set the prev and next pointers accordingly for all
nodes involved
- Included in practice questions for Lab-Tutorial 9
10 20 30
Now we have a new version of list nodes called doubly linked list nodes. These nodes
link with the node after as well as the node before. Therefore, the struct type also has
to be changed to double list node.
So, in addition to the item and the next pointer, we have the previous pointer. This is
powerful because when we insert a node to a linked list now, we would need to
check whether four pointers are set correctly while earlier it was just two.
In a doubly linked list node, if the next pointer is NULL, it indicates the last node, and
if the previous pointer is NULL, it indicates the first node.
29
22 February 2020
10 30
To be removed
10 30
To be added To be added
20
10 20 30
In the doubly linked list, when inserting a node, you have to make sure that you have
set and remove pointers correctly.
30
22 February 2020
temp = temp->next;
10 20 30
Traversing a doubly linked list is very simple. temp=temp->next gets the address of
the next pointer and stores in the temporary pointer. If we traverse backwards, we
should copy the address of the previous node to the temporary pointer.
31
22 February 2020
10 20 30 40
10 20 30
Now, let's make things a bit more complicated. In the doubly-linked list we have
learned, it has fixed ends where both next pointer of the last node and previous
pointer of the first node are set to NULL. What if we want to allow looping?
What if we want the linked list to become a circular list? Do we need to have
additional variables? NO! The doubly linked list has all the variable to turn it into a
circular doubly linked list. Even singly linked list has all the variables to turn it into a
circular singly-linked list.
To turn a singly-linked list to a circular singly-linked list, we need to take the next
pointer of the last node and link it back to the first node.
To turn a doubly-linked list to a circular doubly-linked list, we need to take the next
pointer of the last node and link it to the first node, and take the previous pointer of
the first node and link it to the last node.
32
CIRCULAR LINKED LISTS
10 20 30 40
10 20
20
30
10
80 40
70 50
60
This is how the circular singly-linked list looks once we expand the image.
34
22 February 2020
Since we can add anything to the linked list struct, I have added a tail pointer to the
struct which always points to the last node of the linked list.
If you can recall what you have learned so far, you will remember that we started with
a small program that adds a new item to the back of the existing list each time. This is
inefficient as we have to traverse the linked list each time we add an item. Now that
we have a tail pointer, we can straightaway jump to the last node and attach the new
node to it.
You will see that this becomes useful when we make use of our linked list struct to
create stack and queue data structures next week.
35
22 February 2020
TODAY
• sizeList() function
36
22 February 2020
ARRAY-BASED LISTS
This is an extra knowledge for you. You can go through this to learn to make use of
arrays and still get most of the benefits of a linked list.
37
22 February 2020
ARRAY-BASED LISTS
38
22 February 2020
ARRAY-BASED LISTS
Original list Add 100 by wrapping around to end Add 100 to beginning
Start 20 20 20
of list
30 30 30 30
55 55 55 End
55
10 10 10 of list 10
Start
of list 100
100 Start
of list
When you run out of space at the beginning of the list, what should you do?
39
22 February 2020
TODAY
• sizeList() function
40
22 February 2020
We learned about linked lists because arrays cannot efficiently add something to the
beginning of the list, add something to the middle or remove something from the
middle of the list.
With a linked list, every item is a separate node which you can move anywhere you
want. But you cannot do random access to linked lists. With array-based lists, you can
perform random access.
Random access is just the ability to get directly to, say, number three or element
number five or element number seven, based on the index value.
41
22 February 2020
• Arrays
- Efficient random access
- Difficult to expand, rearrange
- When inserting/removing items in the middle or at the
beginning, computation time scales with size of list
- Generally a better choice when data is immutable
• Linked Lists
- “Random access” can be implemented, but more
inefficient than arrays
- Excellent for dynamic lists
- Easy to expand, shrink, rearrange
- Insert/remove operations only require fixed number of
operations regardless of list size
• Know when to choose an array or a linked list
Linked lists are great for any list that is dynamic. Linked lists can handle size changes,
arrangement changes, etc. easier than arrays. Linked lists take a fixed number of
operations to perform any kind of an arrangement or movement. But, with an array,
to insert an item to the beginning, you need to move everything down. And if the size
is increased, the steps also get increased.
42
22 February 2020
TODAY
• sizeList() function
We have covered the first proper linear dynamic data structure. After this, we
are going to use the foundation of the linked list to move on the stacks and
queues.
43