You are on page 1of 14

CS301 Data Structures

Lecture No. 04
___________________________________________________________________

Data Structures
Lecture No. 04
Reading Material
Data Structures and algorithm analysis in C++

Chapter. 3
3.2.3, 3.2.4, 3.2.5

Summary

Methods of Linked List


Example of list usage
Analysis of Link List
Doubly-linked List
Circularly-linked lists
Josephus Problem

Methods of Linked List


In the previous lecture, we discussed the methods of linked list. These methods form
the interface of the link list. For further elucidation of these techniques, we will talk
about the start method that has the following code.
// position currentNode and lastCurrentNode at first element
void start() {
lastCurrentNode = headNode;
currentNode = headNode;
};
There are two statements in this method. We assign the value of headNode to both
lastCurrentNode and currentNode. These two pointers point at different nodes of the
list. Here we have pointed both of these pointers at the start of the list. On calling
some other method like next, these pointers will move forward. As we can move in
the singly-linked list in one direction, these pointers cannot go behind headNode.
We will now see how a node can be removed from the link list. We use the method
remove for this purpose.
void remove() {
if( currentNode != NULL && currentNode != headNode) {
(step 1)
lastCurrentNode->setNext(currentNode->getNext());

Page 1 of 14

CS301 Data Structures


Lecture No. 04
___________________________________________________________________
(step 2)
(step 3)
(step 4)

delete currentNode;
currentNode = lastCurrentNode->getNext();
size--;
}

};
currentNode
headNode
6

Size = 5

lastCurrentNode
Suppose that the currentNode is pointing at the location that contains the value 6. A
request for the removal of the node is made. Resultantly, the node pointed by
currentNode should be removed. For this purpose, at first, the next pointer of the node
with value 2 (the node pointed by the lastCurrentNode pointer), that is before the
node with value 6, bypasses the node with value 6. It is, now pointing to the node with
value 8. The code of the first step is as:
lastCurrentNode->setNext(currentNode->getNext());
What does the statement currentNode->getNext() do? The currentNode is pointing to
the node with value 6 while the next of this node is pointing to the node with value 8.
That is the next pointer of node with value 6 contains the address of the node with
value 8. The statement lastCurrentNode->setNext(currentNode->getNext()) will set
the next pointer of the node pointed by the lastCurrentNode to the node with value 8.
So the next pointer of the node with value 2 is pointing to the node with value 8.
currentNode
headNode

Step1
2

Size = 5

lastCurrentNode

You see that the next pointer of the node having data element 2 contains the address
of the node having data element 8. The node with value 6 has been disconnected from
the chain while the node with value 2 is connected to the node with the value 8.
The code of the next step is:
delete currentNode;
Page 2 of 14

CS301 Data Structures


Lecture No. 04
___________________________________________________________________
You already know, in case of allocation of the memory with the help of the new
keyword, the delete statement releases this memory which returns the memory to the
system. Pictorially it can be represented as:
currentNode
Step1

headNode

Size = 5

Step2
lastCurrentNode
In the next step, we have moved the currentNode to point the next node. The code is:
currentNode = lastCurrentNode->getNext();
In the fourth step, the size of the list has been reduced by 1 after the deletion of one
node i.e.
size--;
Step3
currentNode
Step1

headNode

Step4

Size = 4

Step2
lastCurrentNode
The next method is length() that simply returns the size of the list. The code is as
follows:
// returns the size of the list
int length()
{
return size;
};
The private data members of the list are:
private:
int size;

// contains the size of the list

Page 3 of 14

CS301 Data Structures


Lecture No. 04
___________________________________________________________________
Node *headNode;
// points to the first node of the list
Node *currentNode, // current node
Node *lastCurrentNode; // last current node
The list class completed just now, can be termed as list factory. We have included all
the required methods in it. We may employ more methods if required. A programmer
can get the size of the list, add or remove nodes in it besides moving the pointers.

Example of list usage


Now lets see how we use the link list. Here is an example showing the use of list:
/* A simple example showing the use of link list */
#include <iostream>
#include <stdlib.h>
#include "List.cpp" // This contains the definition of List class
// main method
int main(int argc, char *argv[])
{
List list; // creating a list object
// adding values to the list
list.add(5);
list.add(13);
list.add(4);
list.add(8);
list.add(24);
list.add(48);
list.add(12);
// calling the start method of the list
list.start();
// printing all the elements of the list
while (list.next())
cout << "List Element: "<< list.get()<<endl;
}
The output of the program is:
List Element: 5
List Element: 13
List Element: 4
List Element: 8
List Element: 24
List Element: 48
List Element: 12
Page 4 of 14

CS301 Data Structures


Lecture No. 04
___________________________________________________________________
Lets discuss the code of the above program. We have included the standard libraries
besides having the List.cpp file. Usually we do not include .cpp files. Rather, the .h
files are included. Whenever you write a class, two files will be created i.e. .h (header
file containing the interface of the class) and .cpp (implementation file). Here for the
sake of explanation, we have combined the two files into List.cpp file. At the start
of the main method, we have created a list object as:
List list;
Here the default constructor will be called. If you understand the concept of factory,
then it is not difficult to know that we have asked the List factory to create a List
object and named it as list. After creating the object, nodes have been added to it. We
have added the elements with data values 5, 13, 4, 8, 24, 48 and 12. Later, the start()
method of list is called that will position the currentNode and lastCurrentNode at the
start of the list. Now there is no need to worry about the implementation of the List.
Rather, we will use the interface of the List. So the start method will take us to the
start of the list and internally, it may be array or link list or some other
implementation. Then there is a while loop that calls the next() method of the List. It
moves the pointer ahead and returns a boolean value i.e. true or false. When we reach
at the end of the list, the next() method will return false. In the while loop we have a
cout statement that prints the value of the list elements, employing the get() method.
The loop will continue till the next() method returns true. When the pointers reach at
the end of the list the next() will return false. Here the loop will come to an end.
Please keep in mind that list is a very important data structure that will be used in the
entire programming courses.

Analysis of Link List


As stated earlier, we will be going to analyze each data structure. We will see whether
it is useful or not. We will see its cost and benefit with respect to time and memory.
Let us analyze the link list which we have created with the dynamic memory
allocation in a chain form.

add
For the addition purposes, we simply insert the new node after the current node.
So add is a one-step operation. We insert a new node after the current node in the
chain. For this, we have to change two or three pointers while changing the values
of some pointer variables. However, there is no need of traversing too much in the
list. In case of an array, if we have to add an element in the centre of the array, the
space for it is created at first. For this, all the elements that are after the current
pointer in the array, should be shifted one place to the right. Suppose if we have to
insert the element in the start of the array, all the elements to the right one spot are
shifted. However, for the link list, it is not something relevant. In link lists, we can
create a new node very easily where the current pointer is pointing. We have to
adjust two or three pointers. Its cost, in terms of CPU time or computing time, is
not much as compared to the one with the use of arrays.

remove
Page 5 of 14

CS301 Data Structures


Lecture No. 04
___________________________________________________________________
Remove is also a one-step operation. The node before and after the node to be
removed is connected to each other. Update the current pointer. Then the node to
be removed is deleted. As a result, the node to be removed is deleted. Very little
work is needed in this case. If you compare it with arrays, for the deletion of an
element from the array, space is created. To fill this space, all the right elements
are shifted one spot left. If the array size is two thousand or three thousand, we
need to run a loop for all these elements to shift them to left.

find
The worst-case in find is that we may have to search the entire list. In find, we
have to search some particular element say x. If found, the currentNode pointer is
moved at that node. As there is no order in the list, we have to start search from
the beginning of the list. We have to check the value of each node and compare it
with x (value to be searched). If found, it returns true and points the currentNode
pointer at that node otherwise return false. Suppose that x is not in the list, in this
case, we have to search the list from start to end and return false. This is the worst
case scenario. Though time gets wasted, yet we find the answer that x is not in the
list. If we compare this with array, it will be the same. We dont know whether x is
in the array or not. So we have to search the complete array. In case of finding it,
we will remember that position and will return true. What is the average case? x
can be found at the first position , in the middle or at the end of the list. So on
average, we have to search half of the list.

back
In the back method, we move the current pointer one position back. Moving the
current pointer back, one requires traversing the list from the start until the node
whose next pointer points to current node. Our link list is singly linked list i.e. we
can move in one direction from start towards end. Suppose our currentNode
pointer and lastCurrentNode are somewhere in the middle of the list. Now we
want to move one node back. If we have the pointer of lastCurrentNode, it will be
easy. We will assign the value of lastCurrentNode to currentNode. But how can
we move the lastCurrentNode one step back. We dont have the pointer of
previous node. So the solution for this is to go at the start of the list and traverse
the list till the time you reach the node before the lastCurrentNode is pointing.
That will be the node whose next pointer contains the value lastCurrentNode. If
the currentNode and the lastCurrentNode are at the end of the list, we have to
traverse the whole list. Therefore back operation is not a one step operation. We
not only need a loop here but also require time.

Doubly-linked List
If you look at single link list, the chain is seen formed in a way that every node has a
field next that point to the next node. This continues till the last node where we set the
next to NULL i.e. the end of the list. There is a headNode pointer that points to the
start of the list. We have seen that moving forward is easy in single link list but going
back is difficult. For moving backward, we have to go at the start of the list and begin
from there. Do you need a list in which one has to move back or forward or at the start
or in the end very often? If so, we have to use double link list.
In doubly-link list, a programmer uses two pointers in the node, i.e. one to point to
Page 6 of 14

CS301 Data Structures


Lecture No. 04
___________________________________________________________________
next node and the other to point to the previous node. Now our node factory will
create a node with three parts.

prev

element

next

First part is prev i.e. the pointer pointing to the previous node, second part is element,
containing the data to be inserted in the list. The third part is next pointer that points to
the next node of the list. The objective of prev is to store the address of the previous
node.
Lets discuss the code of the node of the doubly-link list. This node factory will create
nodes, each having two pointers. The interface methods are same as used in singly
link list. The additional methods are getPrev and setPrev. The method getPrev returns
the address of the previous node. Thus its return type is Node*. The setPrev method
sets the prev pointer. If we have to assign some address to prev pointer, we will call
this method. Following is the code of the doubly-linked list node.
/* this is the doubly-linked list class, it uses the next and prev pointers */
class Node {
public:
int get() { return object; }; // returns the value of the element
void set(int object) { this->object = object; }; // set the value of the element
Node* getNext() { return nextNode; }; // get the address of the next node
void setNext(Node* nextNode)
// set the address of the next node
{ this->nextNode = nextNode; };
Node* getPrev() { return prevNode; }; // get the address of the prev node
void setPrev(Node* prevNode)
// set the address of the prev node
{ this->prevNode = prevNode; };
private:
int object;
// it stores the actual value of the element
Node* nextNode;
// this points to the next node
Node* prevNode;
// this points to the previous node
};

Most of the methods are same as those in singly linked list. A new pointer prevNode is
added and the methods to get and set its value i.e. getPrev and setPrev. Now we will
use this node factory to create nodes.
You have to be very cautious while adding or removing a node in a doubly linked list.
The order in which pointers are reorganized is important. Lets have a pictorial view
of doubly-link list. The diagram can help us understand where the prevNode and
nextNode are pointing.
Page 7 of 14

CS301 Data Structures


Lecture No. 04
___________________________________________________________________

head

size=5

current
This is a doubly link list. The arrows pointing towards right side are representing
nextNode while those pointing towards left side are representing prevNode. Suppose
we are at the last node i.e. the node with value 1. In case of going back, we usually
take the help of prevNode pointer. So we can go to the previous node i.e. the node
with value 7 and then to the node with value 8 and so on. In this way, we can traverse
the list from the end to start. We can move forward or backward in doubly-link list
very easily. We have developed this facility for the users to move in the list easily.
Lets discuss other methods of the doubly-linked list. Suppose we have created a new
node from the factory with value 9. We will request the node factory to create a new
object using new keyword. The newly created node contains three fields i.e. object,
prevNode and nextNode. We will store 9 into object and connect this new node in the
chain. Lets see how the pointers are manipulated to do that. Consider the above
diagram, the current is pointing at the node with value 6. The new node will be
inserted between the node with value 6 and the one with value 8.
In the first step, we assign the address of the node with value 8 to the nextNode of the
new node.
newNode->setNext( current->getNext() );
current

head

newNode

size=5

In the next step, a programmer points the prevNode of the newNode to the node with
value 6.
newNode->setprev( current );

Page 8 of 14

CS301 Data Structures


Lecture No. 04
___________________________________________________________________
current

head

size=5

2
newNode

In the third step, we will set the previous node with value 8 to point to the newNode.
(current->getNext())->setPrev(newNode);
current

head

size=5

newNode

Now the prevNode of the node with value 8 is pointing to the node with value 9.
In the fourth step, the nextNode of the node with value 6 is pointing to the newNode
i.e. the node with value 9. Point the current to the newNode and add one to the size of
the list.
current->setNext( newNode );
current = newNode;
size++;

head

6
4

2
newNode

size=6

3
1

current

Now the newNode has been inserted between node with value 6 and node with value
8.

Page 9 of 14

CS301 Data Structures


Lecture No. 04
___________________________________________________________________

Circularly-linked lists
Lets talk about circularly linked list. The next field in the last node in a singly-linked
list is set to NULL. The same is the case in the doubly-linked list. Moving along a
singly-linked list has to be done in a watchful manner. Doubly-linked lists have two
NULL pointers i.e. prev in the first node and next in the last node. A way around this
potential hazard is to link the last node with the first node in the list to create a
circularly-linked list.
The next method in the singly-linked list or doubly-linked list moves the current
pointer to the next node and every time it checks whether the next pointer is NULL or
not. Similarly the back method in the double-linked list has to be employed carefully
if the current is pointing the first node. In this case, the prev pointer is pointing to
NULL. If we do not take care of this, the current will be pointing to NULL. So if we
try to access the NULL pointer, it will result in an error. To avoid this, we can make a
circularly linked list.
We have a list with five elements. We have connected the last node with the first node.
It means that the next of the last node is pointing towards the first node.
current
head

size=5

The same list has been shown in a circular shape.


current
6
8
head

size=5

2
7
1

You have noticed that there is no such node whose next field is NULL. What is the
benefit of this? If you use the next or back methods that move the current pointer, it
will never point to NULL. It may be the case that you keep on circulating in the list.
To avoid this, we get help from the head node. If we move the head node in the
circularly linked list, it will not be certain to say where it was pointing in the start. Its
advantages depend on its use. If we do not have to move too much in the list and have
Page 10 of 14

CS301 Data Structures


Lecture No. 04
___________________________________________________________________
no problem checking the NULL, there is little need a circularly-linked list. But this
facility is available to us.
In this example, we made a circular linked list from a singly link list. In a singly link
list we move in one direction. We point the next pointer of the last node to the first
node. We can do the same with the doubly-linked list. The prev pointer of the first
node will point to the last node and the next pointer of the last node will point to the
first node. If you arrange all the nodes in a circle, one of the pointers (i.e. next
pointer) will move in clockwise direction while the prev pointers in anti-clockwise
direction. With the help of these pointers, you can move in the clockwise direction or
anti-clockwise direction. Head node pointer will remain at its position. You dont need
to change it. If there is a need to remove the node pointed by head node than you have
to move the head pointer to other node. Now we dont have any NULL pointer in the
doubly-linked list. We will not get any exception due to NULL pointers.

Josephus Problem
Now we will see an example where circular link list is very useful. This is Josephus
Problem. Consider there are 10 persons. They would like to choose a leader. The way
they decide is that all 10 sit in a circle. They start a count with person 1 and go in
clockwise direction and skip 3. Person 4 reached is eliminated. The count starts with
the fifth and the next person to go is the fourth in count. Eventually, a single person
remains.
You might ask why someone has to choose a leader in this way. There are some
historical stories attached to it. This problem is also studied in mathematics. Lets see
its pictorial view.
N=10, M=3
4

10
9

We have ten numbers representing the ten persons who are in a circle. The value of M
shows the count. As the value of M is three, the count will be three. N represents the
number of persons. Now we start counting clockwise. After counting up to three, we
have the number four. The number four is eliminated and put in the eliminated
column.

Page 11 of 14

CS301 Data Structures


Lecture No. 04
___________________________________________________________________
Eliminated

N=10, M=3

10
9

After eliminating the number four, we will start our counting from number five.
Counting up to three, we have number eight which is eliminated and so on.
Eliminated
N=10, M=3
4
3
5

8
6

10
9

In the end, only number five will remain intact.


Eliminated
4

N=10, M=3
5

8
2
7
3

10
9
1
6

Page 12 of 14

CS301 Data Structures


Lecture No. 04
___________________________________________________________________
If we have ten persons (N = 10) in a circle and eliminate after counting up to three (M
= 3). If we start our count from one, who will be the leader? We have studied this
earlier and know that the person who is sitting at the fifth position will become the
leader.
Suppose if the value of N is 300 or 400 and the value of M is 5 or 10. Now who will
be the leader? This is a mathematical problem where we can change the values of N
and M. There is a formula where the values of N, M are allotted. You can calculate
who should become the leader. Here we will not solve it mathematically. Rather, it
will be tackled as a computer problem. If you analyze the pictures shown above, it
gets clear that this can be solved with the circular link list. We arrange these numbers
in a circularly-linked list, point the head pointer at the starting number and after
calling the next method for three times, we will reach the node which is to be
removed. We will use the remove method to remove the node. Then the next method is
called thrice from there and the node is removed. We will continue this till we have
only one node.
We are not concerned with the NULL pointers, internal to link list. However, if you
want to solve this problem and choose the best data structure, then circular link list is
the best option. We can also use the list to solve this.
Lets see the code of the program by which we can solve this problem. The code is as
under:
/*This program solves the Josephus Problem */
#include <iostream.h>
#include "CList.cpp" //contains the circularly-linked list definition
// The main method
void main(int argc, char *argv[])
{
CList list;
// creating an object of list
int i, N=10, M=3;
for(i=1; i <= N; i++ ) list.add(i); // initializing the list with values
list.start();

// pointing the pointers at the start of the list

// counting upto M times and removing the element


while( list.length() > 1 ) {
for(i=1; i <= M; i++ ) list.next();
cout << "remove: " << list.get() << endl;
list.remove();
}
cout << "leader is: " << list.get() << endl;

//displaying the remaining node

Page 13 of 14

CS301 Data Structures


Lecture No. 04
___________________________________________________________________
We have included the CList.cpp. It means that we are using the circularly-linked
list. In the main method, CList factory is called to create a circular link list as CList
list; After this, we assign the values to N and M. We have used for loop to add the
nodes in the list. When this loop finishes, we have ten nodes in the list having values
from 1 to 10. But here a programmer may not pay attention to the internal details of
the list. We have created a list and stored ten numbers in it. Then we moved the
pointers of the list at the start of the list using the start method. It means that the
pointers are pointing at the position from where we want to start the counting of the
list.
There is a while loop that will continue executing until only one node is left in the list.
Inside this loop, we have a for loop. It will execute from 1 to M. It has only one
statement i.e. list.next(). This will move the pointer forward three times (as the value
of M is 3). Now the current pointer is at the 4 th node. We called the remove method.
Before removing the node, we display its value on the screen using cout. Again we
come into the while loop, now the length of the list is 9. The for loop will be
executed. Now the list.next() is not starting from the start. It will start from the
position where the current pointer is pointing. The current pointer is pointing at the
next node to the node deleted. The count will start again. The list.next() will be called
for three times. The current pointer will point at the 8 th node. Again the remove
method will be called and the current pointer moved to the next node and so on. The
nodes will be deleted one by one until the length of the list is greater than one. When
the length of the list is one, the while loop will be terminated. Now only one node is
left in the list i.e. the leader. We will display its value using the get method.
We can change the values of M and N. Similarly, these values can be read from the
file or can use the command line arguments to get values. There are many variations
of this problem. One variation is that the value of M keeps on changing. Sometimes, it
is 3, sometimes 4 or 5 and so on. Due to this, it will become difficult to think that who
will become leader. Make a picture in your mind that ten persons are sitting in a
circle. Every time the value of M is incremented by one. Now try to ascertain which
position you should sit to get chosen as a leader. You may like to write a program to
solve this or use the mathematical formula.

Page 14 of 14

You might also like