Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                

Notes On Data Structures and Algorithms: Dr. Anindita Kundu

Download as pdf or txt
Download as pdf or txt
You are on page 1of 64

Notes on Data Structures and Algorithms

Dr. Anindita Kundu


Contents

I Introduction
1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
1.1 Why do we need data structure and algorithms? 7
1.2 Complexity and Efficiency of Algorithms 14
1.3 Machine Independent Model of Computation 15

2 Linear Data structures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19


2.1 Arrays 19
2.1.1 Types of indexing in array: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
2.1.2 Methods for storing multidimensional arrays in linear storage . . . . . . . . . . . . . . 20
2.1.3 Array Cell Address Calculation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
2.2 Sparse Matrix 23
2.2.1 Is it efficient to use a 2-D array for a sparse matrix? . . . . . . . . . . . . . . . . . . . . . . 23
2.2.2 Sparse Matrix Representation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
2.2.3 Triplet Representation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
2.2.4 Compressed Sparse Row (CSR) or Yale Representation . . . . . . . . . . . . . . . . . . 24
2.2.5 Addition of Sparse Matrices in Triplet Format . . . . . . . . . . . . . . . . . . . . . . . . . . 25
2.2.6 Product of Sparse Matrices in Triplet Format . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
2.3 Polynomial Representation using Array 30
2.3.1 Points to keep in Mind while working with Polynomials: . . . . . . . . . . . . . . . . . . . 30
2.4 Abstract Data Type 31
2.5 Linked List 33
2.5.1 Advantages of a Linked List over Array . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
2.5.2 Disadvantages of a Linked List over Array . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
2.5.3 Self Referential Structure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
2.5.4 Linked List Operations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
2.5.5 Creating a node . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
2.5.6 Creating a Singly Linked List . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
2.5.7 Displaying a Singly Linked List and Counting the number of nodes in the list . . 38
2.5.8 Inserting a new node in a Singly Linked List . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
2.5.9 Deletion of nodes from a Singly Linked List . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
2.5.10 Circular Singly Linked List . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
2.5.11 Doubly Linked List . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
2.5.12 Circular Doubly Linked List . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
2.6 Stacks 50
2.7 Queues 50

II Module II

III Module III

IV Laboratory Assignments

3 Lab and Home Assignment Sheet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57


3.1 Introduction, Arrays, Linked Lists 58
3.1.1 Day 1: Time and Space Complexity . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
3.1.2 Day 2: Array . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
3.1.3 Day 3: Single Linked List . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
3.1.4 Day 4: Circular and Doubly Linked List . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
3.2 Linear Data Structures 59
3.2.1 Day 5: Stack, Queue - with array . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
3.2.2 Day 6: Stack, Queue - with linked list . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
3.2.3 Day 7: Circular Queue, Deque - with linked list . . . . . . . . . . . . . . . . . . . . . . . . . 60
3.3 Non-linear Data Structures 60
3.3.1 Day 8: Binary Search Tree (BST) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
3.4 Algorithms 60
3.4.1 Day 9: Searching . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
3.4.2 Day 10: Sorting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
3.4.3 Day 11: Graph Algorithms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61

Bibliography . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
I
Introduction

1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . 7
1.1 Why do we need data structure and algorithms?
1.2 Complexity and Efficiency of Algorithms
1.3 Machine Independent Model of Computation

2 Linear Data structures . . . . . . . . . . . . . . . . 19


2.1 Arrays
2.2 Sparse Matrix
2.3 Polynomial Representation using Array
2.4 Abstract Data Type
2.5 Linked List
2.6 Stacks
2.7 Queues
1. Introduction

1.1 Why do we need data structure and algorithms?


Good Morning Everybody. Today is the first class on the course titled “Data structures and
Algorithms”. You will find from the title that there are apparently 2 aspects of the course. One is
data structures and the other is algorithms designing. However, these 2 aspects are quite integrated
in nature and they go hand in hand in solving any problem for which a computer program is required
to be developed. Now, the core issue which require both algorithms and data structuring, lies in one
point, i.e. solving a problem EFFICIENTLY.
Unless we understand how to solve a problem efficiently on a computer, and what are the steps
to do it, it will be very difficult to understand what algorithm and data structuring means because
these 2 are the derived items of the system of problem solving. Now, the first thing to remember
is that we are solving problems on a computer and the computer has got a particular structure, a
particular architecture and we are going to solve problems based on those issues. Problem solving is
not merely a process of programming. It is the process of developing a solution to a problem which
is expressed in a natural form. For example, when given a problem of finding the max of n numbers,
this problem is posed in a natural language. It is our duty and task to convey it to a computer
which will solve the problem in a language that it understands. If we had the technology which
can understand the natural language itself, then we would not have required this series of tasks of
programming or the concept of data structures and algorithms would have been automated already.
So, since we do not have such a technology, and a computer can understand only a restricted form
of language. Usually a problem is posed in a natural language, and it is our task to convert this
problem into the language of computers so that it works efficiently. So this is the core of problem
solving.
Problem Solving requires a lot of issues other than peer programming. The first aspect is when
a problem is expressed in a natural language, say find the maximum of n numbers, we are not told
what are the steps that we need to follow to find the desired result. The question of steps come in
because of the architecture of the computer itself. The computer is unable to understand our visual
expression. We cannot give a diagonal of n numbers to a computer just like you can give to a human
being who will just observe and give you the answer. So the question of steps is an inherent issue
8 Chapter 1. Introduction

that is related to the architecture of the computer. Therefore the first aspect of problem solving is
to convert it to a sequence of steps and how do we do that is something that we ourselves figure
out. So, the terms like data structuring, algorithm design, programming methodology, all deal with
converting the problem given to you to the sequence of steps that can be executed in the computer.
It is very difficult to define, understand and present an automated way of saying that this is the
theory of problem solving. There is no well-defined theory of problem solving till date in which
you will express a problem in a natural language and the problem will automatically be solved by
the machine. Partial solution to such automated problem solving has been achieved in artificial
intelligence but it is long way to go before we actually understand what automated problem solving
is because it is one of the goals of computer science itself. Problem Solving as we understand it is a
mixture of techniques, intuition, and other aspects which have formed a body of knowledge. This
body of knowledge has to be understood in order to really solve individual problems at hand. Our
task in this course will be to study this body of knowledge through various forms so that we are
able to use it and develop new techniques in future. There are several ways in which we can try to
impart this knowledge to others and that is what we will learn through this course.
 Example 1.1 Find maximum of n integers. 

Since you have already covered the course on programming what will come to your mind is
something like Algorithm 1.1.

Algorithm 1.1 Find the maximum of n numbers


1: Read n
2: Read first number and store it as MAX
3: i ← 1
4: for i ≤ n − 1 do
5: Read number and store it as NUM
6: if NUM > MAX then
7: MAX ← NUM
8: end if
9: end for
10: Display MAX

This is NOT Problem Solving. You could say it because you were TAUGHT to instruct a
computer in this fashion to find the solution. Given a new problem of which you are presently
unaware of, how will you tackle it or approach it? When this problem is given to a person for
the first time how will he/she approaches the problem? That is problem solving. It involves some
thinking skills which we call problem decomposition. In this course we will learn how to develop
that thinking skill and see how solutions to problems can be achieved efficiently.

Figure 1.1: Problem Decomposition

Let’s take a deeper look at the problem. Let T be a set of n numbers such that T = {t1 ,t2 ,t3 , . . . ,tn }.
Firstly we consider the problem comprising of only 1 number, i.e. t1 and solve it. Since, there
is only 1 number, it is considered to be the MAX. Next we use the solution of this problem and
1.1 Why do we need data structure and algorithms? 9

compare it with the next number i.e. t2 . In solving the problem at this stage the solution of the
1st stage is taken into consideration. Similarly, for the subsequent iteration say ti , the solution till
iteration ti−1 is taken into consideration. This technique is called problem decomposition. Figure
1.1 illustrates the scenario. This is a very simple and trivial problem. Once you know this approach
you may start thinking if there exists any other decomposition method as well! Let’s see how can
we do it?
As shown in Figure 1.2, lets compare t1 and t2 and store the maximum of the 2 as MAX1 and
then compare rest of the elements in a similar independent fashion and say the maximum of the
numbers is MAX2 . Now, we can get the solution by simply comparing MAX1 and MAX2 . So, this
shows that instead of decomposing the problem 1 by 1, we could have decomposed it into many
other ways and could have found solution.

Figure 1.2: General Problem Decomposition

The algorithm for this method is given by Algorithm 1.2. If you take a closer look you will
realize that Algorithm 1.1 is a special case of Algorithm 1.2.

Algorithm 1.2 maximum(n)


Let T ← {t1 ,t2 ,t3 , . . . ,tn }
Split T into 2 non-empty disjoint sets say T1 and T2
MAX1 ← maximum(T1 )
MAX2 ← maximum(T2 )
if MAX1 > MAX2 then
MAX ← MAX1
else
MAX ← MAX2
end if
Return/Display MAX

Thus, the broad (but not hard and fast) steps in solving a problem can be stated as following:

• Initial Solution Generation: Whenever you are given a problem, try and solve the problem
posed to you in some form. You have to come up with an inherent solution of the problem,
i.e., you need to solve/decompose the problem in your mind before solving it on a computer.
Let us consider the general decomposition of the problem that we discussed as the initial
solution. Given an initial solution, we can actually encompass a lot of possible final solutions.
In this example the possible final solution depends on how you split the set into 2 subsets.
Depending upon how you split the subset, your initial solution may contains a set of actual
potential programs which has to give correct results. Correctness of the solutions have to be
ensured in this stage. Its like solving a problem in geometry which no one can teach you.
You solve problems based on some theorems which you are taught and your experience of
solving previous problems using your intelligence which is something we do not understand
properly. Similarly, over here you have to have insight into the problem, and know several
problem solving techniques and using them along with your intelligence to generate the initial
solution. Any 2 people can generate 2 completely different initial solution for a particular
10 Chapter 1. Introduction

given problem.

• Initial Solution Refinement: Once the possible set of potential solutions have been formu-
lated, you have to refine and choose the best possible solution among the set. The criteria for
choosing the best solution is efficiency. Efficiency means 2 things. One is time efficiency
and the other is space efficiency. We have to try and ensure that our programs run as fast as
possible and get them automated. Space efficiency is required because space is at a premium.
Today memory technology has evolved so that we can have lot of memory but there are
still problems where space will be a constraint. For example, in case of sensor motes or
devices involved in IoT. You may also have to maintain a space time tradeoff, i.e. increase
space requirement to reduce time and vice-versa depending upon the scenario. This is where
the steps or the standard methodology of programming and data structures come in. Once
you are given an initial solution, there are certain techniques which are now quite standard
and well known to evaluate whether the solution is good or bad. These form part of data
structures and algorithms. Here we come across the terms like Algorithm Designing and
Data Structuring. Algorithms are the exact sequence of steps that has to be followed to
get the potential solution and Data structuring is the organization of the data. For example,
organizing T in this case is splitting and storing T1 and T2 . How do we do that? Do we use
arrays or lists to store them or do we just take them as input on the go. Data structuring goes
hand in hand with algorithm design to give you the final solution.

• Final Solution: The final solution is the most efficient solution from the set of potential
solutions that we could design. Now, comes the step of programming. Given the solution in
some sequence of steps in some language of our own (pseudo code or flowchart) we now
have to write a program and in order to do so we have to choose a vehicle language. If
given a choice, we have to choose a vehicle language that is most close to the language
used to write the pseudo code. The high level language that we choose, should provide
features for Data Structuring like Data Types and Operations.For example, if a language
allows you to SET data type that will be more useful than an array or a linked list. Some
languages support Abstract Data Types and the some languages especially the object oriented
programming languages support Data-Procedure Encapsulation (which is a Data structuring
technique). Some languages also support Dynamic Data Structuring e.g. dynamic memory
allocation. For example, if the value of n is not given apriori, and a language can adapt to the
requirement by dynamic memory allocation that is definitely going to be useful. Related to
the algorithm design, these languages provides the control constructs for example if, else,
for, while, etc. which are all known to us and for decomposition, it supports functions and
Recursive Functions. In our study here, we will use C as our vehicle language.
Thus, in this course we will learn what is problem solving? what is data structuring? what
is algorithm designing and how data structuring and algorithm designing goes hand in hand in
problem solving?
 Example 1.2 Find the maximum as well as minimum of n distinct numbers. 

Now, that you already know how to find the maximum of n numbers, if I ask you to find the
maximum as well as the minimum of n numbers I am sure from my experience that majority of
you will follow the algorithm 1.3 to find the solution. Please note that the number of comparisons
made in the algorithm is 2(n − 1) (n − 1 times for MAX and n − 1 times for MIN). This is an initial
solution that has been generated.
Now, we can refine the algorithm 1.3 by trying to lower the number of comparisons. One of the
refinement of the initial solution can be as given by algorithm 1.4. In this case, the comparison
for checking minimum will be executed only if the condition for maximum fails. Thus, the number
1.1 Why do we need data structure and algorithms? 11

of comparisons in this case will be ≤ 2(n − 1). However, in the worst case scenario, the number of
comparisons for both the algorithms 1.3 and 1.4 remains 2(n − 1).

Algorithm 1.3 Finding maximum as well as minimum of n numbers


Read n
Read a number MAX
MIN ← MAX
i←1
for i ≤ n − 1 do
Read number as NUM
if NUM > MAX then
MAX ← NUM
end if
if NUM < MIN then
MIN ← NUM
end if
end for
Display MAX and MIN

Algorithm 1.4 Finding maximum as well as minimum of n numbers using first refinement
Read n
Read a number MAX
MIN ← MAX
i←1
for i ≤ n − 1 do
Read number as NUM
if NUM > MAX then
MAX ← NUM
else
if NUM < MIN then
MIN ← NUM
end if
end if
end for
Display MAX and MIN

Now, let’s take a deeper look at the problem. If we have n numbers, in order to find the
maximum of the n numbers minimum n − 1 comparisons are necessary irrespective of the way the
numbers are compared as shown in Figure 1.3. This scenario is similar to a knockout tournament.
The winner is the highest number. However, in order to find the minimum number, only the
loosers of the first round of the match needs to be compared as shown in Figure 1.4. This is
because if a number wins a knockout it means another number of lower value exists in the set.
Hence, it cannot be the minimum. Now, even though we cannot do anything about minimizing the
number of comparisons to find the largest number, we can do something to minimize the number
of comparisons required to find the minimum number. The number of comparisons for finding
the minimum number will be least if pairwise comparison can be done. Figure 1.5 illustrates the
scenario. In order to find the minimum number of comparisons required to find both the largest and
the smallest number, the number of comparisons required in the first stage is n2 considering n is
even. This is because we are considering pairwise comparison. This is going to result in n2 number
12 Chapter 1. Introduction

for finding the largest number. Now, we know that the minimum number of comparisons required
to find the largest of n numbers is n − 1. Similarly, the minimum number of comparisons required
to find the largest of n2 numbers is n2 − 1. On the other hand, the first set of pairwise comparisons
have also resulted in a set of n2 loosers which are to be compared to find the minimum number. Now,
to find the minimum of n2 numbers, n2 − 1 comparisons are necessary. Therefore, the total number
of comparisons required is n2 + n2 − 1 + n2 − 1 which can be written as 3 2n − 2 which is < 2(n − 1).

Figure 1.3: Finding the maximum

Figure 1.4: Finding the minimum

Figure 1.5: Finding the maximum and minimum of n numbers in least number of comparisons

Exercise 1.1 Find the minimum number of comparisons that will be required to find maximum
and minimum of n numbers considering n is odd. 

Now, the next step will be to convert the refined solution to the final solution. The final solution
can be as given by algorithm 1.5.
1.1 Why do we need data structure and algorithms? 13

Algorithm 1.5 Finding maximum as well as minimum of n numbers using the final solution
(considering n is even)
Read n
i←1
for i ≤ n/2 do
Read 2 numbers NUM1 and NUM2
if NUM1 > NUM2 then
Append NUM1 to set L
Append NUM2 to set M
else
Append NUM2 to set L
Append NUM1 to set M
end if
end for
MAX ← maximum(L)
MIN ← minimum(M)
Display MAX and MIN

Exercise 1.2 Modify the algorithm 1.5 such that it will find both the maximum and minimum
of n numbers efficiently considering n as both even and odd. 

The ultimate stage is to write a program based on the algorithm 1.5.


1 //Find_Max_Min_n_even.c
2 #include<stdio.h>
3 int main()
4 {
5 int i,n,num1,num2;
6 int L[100],M[100],idx1=0,idx2=0;
7 printf("How many numbers: ");
8 scanf("%d",&n);
9 for(i=1;i<=n/2;i++)
10 {
11 printf("Enter 2 numbers: ");
12 scanf("%d%d",&num1,&num2);
13 if(num1<num2)
14 {
15 L[idx1++]=num2;
16 M[idx2++]=num1;
17 }
18 else
19 {
20 L[idx1++]=num1;
21 M[idx2++]=num2;
22 }
23 }
24 printf("`MAX=:%d MIN=:%d",Mx(L,idx1), Mn(M,idx2));
25 return 0;
26 }
27 //Find the maximum
28 Mx(arr,idx)
29 {
30 int i,max;
14 Chapter 1. Introduction

31 max = arr[0];
32 for(i = 1;i < idx; i++){
33 if(max<arr[i]) max = arr[i];
34 }
35 return max;
36 }
37 //Find the minimum
38 Mn(arr,idx)
39 {
40 int i,min;
41 min = arr[0];
42 for(i = 1;i < idx; i++){
43 if(min > arr[i]) min = arr[i];
44 }
45 return min;
46 }

Exercise 1.3 Modify the program Find_Max_Min_n_even.c such that it will find both the
maximum and minimum of n numbers efficiently considering n as both even and odd. 

Exercise 1.4 Device an efficient algorithm to find the highest and the second highest numbers
from a group of the n numbers considering n to be both odd and even. [Hint: You have to use
the tournament structure] 

1.2 Complexity and Efficiency of Algorithms


After we have gained some idea about the organization of data and the performance of algorithms,
in this section we will discuss about the measures of comparison of 2 or more algorithms. We will
see exactly when a program or algorithm is said to be better than the other. The foundation of all
these things will lie in the concept of efficiency of an algorithm or the complexity of an algorithm
and this idea has to be clear before we move towards designing good algorithms and data structures.
To understand this, we have to answer 2 questions. First one is when is a program or algorithm said
to be better than the other? and the second one is what are the measures for comparison?
If we run 2 programs on 2 different machines, they will take 2 different amount of time. Also,
one program may be much bigger in size than the other program. It may also happen that one
program may take more space than the other program. Or maybe one program is much easy to
understand than the other program. So in terms of all these things, when do we say that a program
is actually better than the other program? Unless we understand the concept of the term ‘better’
than, we will not be able to realize the concept of ‘best’. And that is what we are trying to find out.
What is the best algorithm for a particular problem.
To answer the question we must first be able to decide what are the measures of the comparison
of 2 algorithms. They are:
• Time required: It is also called the Time Complexity of an algorithm. It is the execution time
of the algorithm, not the compilation time.
• Space required:
– Program Size: Does not change with input.
– Data Size: May change with input or data may be generated or stored dynamically.
One program may be long is size but uses a 1-D array of size n to solve a problem while
another program may appear to be small but takes a 3-D array of size n × n × n to solve
the problem. The first program has large program size but smaller data size while the later
1.3 Machine Independent Model of Computation 15

program has small program size but large data size. Hence, the program size along with the
data size, together they form what is called the Space Complexity of an algorithm.
Both the space and time together decide how good an algorithm is. Unless the space required is
exponential or exorbitantly high, in most of the cases it is the time required that contributes to the
goodness of the algorithm. Now there are some more issues.
Lets take 2 programs P1 and P2 and run them on 2 machines M1 and M2 . Say it is observed that
P1 runs faster than P2 in M1 while P2 runs faster than P1 in M2 . Can we conclude anything? Of
course not. Moreover, it is not possible to run a program on multiple machines just to decide how
good it is. But if you want to judge them based on CPU time, then obviously we have to run them
on multiple machines which is next to impossible. Hence, we have to judge them using a machine
independent model of the 2 programs. In the next section we will discuss, how to form the machine
independent model of the programs.

1.3 Machine Independent Model of Computation


Can we have a machine independent model of computation? Well yes we can but we have to make
some assumptions and compromises. Firsty, we have to consider and abstract machine to judge the
programs, i.e., we cannot have an actual machine measure the exact cpu times of the programs.
Let us consider 2 machines with same instruction set (a sample instruction set is as given in
Figure 1.6). The machines may have different CPU clock speeds. If we run a program is these 2
machines, their execution times will depend on the time steps of the instructions which will be same
for both the machines and on the CPU clock speeds of both the machines. Since the instruction
sets are same, the time steps required by both the machines will be same. However, the execution
time of the program on the 2 machines will be different and will be proportional to the CPU clock
speeds. This is an assumption that we make.

Figure 1.6: Sample Instruction Set

Now, what if they have different instruction sets? Their speed will be different. This is where
we make another assumption. We consider that if any instruction on a machine takes a constant
amount of time step (i.e. independent of the input size) to do the operation, we consider it to be
1. So an ADD instruction will take 1 time step as it takes constant time. On the other hand, time
16 Chapter 1. Introduction

taken by a multiplication instruction, MULT X1 X2 X3, will depend upon the size of X1 or X2
because when you are multiplying a number X1 with a number X2, you are actually adding X1 X2
number of times. But both X1 and X2 have fixed size. Hence, the number of time steps required
will also be fixed. Hence, the time step will also be considered to be 1. Thus, the assumption is that
all operations in the machine take same unit of time.
Based on the assumptions, we can approximately consider the number of steps that a program
P requires. So when given 2 programs P1 and P2 , we can compare the number of steps they require
to execute. The error incurred in this execution is by a ratio as they are compared based on the
number of time steps.
 Example 1.3 Let us consider the algorithm to find the maximum of 100 numbers.

Algorithm 1.6 Find the maximum of 100 numbers


1: Read MAX
2: i ← 1
3: for i < 100 do
4: Read NUM
5: if NUM > MAX then
6: MAX ← NUM
7: end if
8: i ← i+1
9: end for
10: Display MAX

In this algorithm, line no. 1 reads a number MAX. Now, a number has a fixed size, so reading
a number will take constant time. Hence, lets consider the number of time steps required for this
step to be 1. Similarly, the line no. 2 has an assignment operation which also takes unit time
step. However, for line no. 3, the comparison i < 100 occurs 100 times. Hence, 99 time steps
are required to execute line no. 3. Similarly, the read operation in line no. 4 and the comparison
operation of line no. 5 and the assignment operation in line no. 8 being inside the loop also executes
99 number of times and requires 99 time steps each. In the worst case, the i f condition of line
no. 5 become true for all the 99 times and hence the assignment operation of line no. 6 also gets
executed 99 times thereby requiring 99 time steps. Finally, the display operation of line no. 10 gets
executed only once and since it displays a fixed sized data, it requires fixed time. Hence 1 time
step is required for the execution of line no. 10. So, overall the number of time steps required is
1+1+100+99+99+99+99+1 as given in Figure 1.7. So, the program execution time is fixed.

Figure 1.7: Algorithm with time steps required for execution of each line
1.3 Machine Independent Model of Computation 17

Now, let us consider algorithm 1.1. In this case, the value of n has to be read. The line nos. 1-3
is going to take 1 time step each while line no. 4 is going to be executed in n time steps. Similarly,
line nos. 5-7 will be executed n − 1 times and line no. 10 is going to be executed in a unit time step
as shown in Figure 1.8. So, overall the approximate number of execution time steps required is
4n + 1 which is also called the time complexity of the algorithm.

Figure 1.8: Algorithm 1.1 with time steps required for execution of each line

At this point of time, the 4 in 4n + 1 hardly has any significance. Say, another algorithm A1
has a time complexity of 7n − 2. Algorithm 1.8 has mainly read and assignment operations and
say A1 has mainly multiplication and addition operations. Now, they are run on machines M1 and
M2 . Machine M1 takes more time to perform read and assign while it hardly takes any cpu time for
addition and multiplication operations. On the other hand machine M2 takes more time for addition
and multiplication operations while it hardly takes any time for read and assignment. If we run
algorithm A1 on M1 and algorithm 1.8 on M2 or vice versa the cpu time required by them may be
almost equal. So, we cannot conclude which one is better. Actually, both of them will take time
proportional to n.
If we compare 2 algorithms which requires 5n + 200 and 3n2 + 2n + 6, we can say that the first
algorithm is better than the later as for large value of n, the number of time steps for the later will
be significantly higher compared to the first. So the constants hardly play any role.
However, if P1 takes 5n+200, P2 takes 2n2 −3n+2, P3 takes 2n+20 and P4 takes 3n2 +5n+10,
based on Figure 1.9 we can say that, P1 and P3 will be faster than P2 and P4 irrespective of the
constants.

Figure 1.9: Performance of programs based on input size


18 Chapter 1. Introduction

So we can say that, if P1 takes c1 n + c2 and P2 takes c3 n2 + c4 n + c5 , where c1 , c2 , c3 , c4 and c5


are constants, and c1 and c3 are positive constants, based on the Figure 1.9 (a) we can say that P1 is
better than P2 for n > n0 . Overall, the effect of variation of various orders of n with increase in its
value is given by Figure 1.10.

Figure 1.10: f (n) based on input size n


2. Linear Data structures

A data structure is said to be linear if its elements form a sequence or a linear list. E.g.: Array,
Linked List, Stacks, Queues. The operations on linear data structures involve:
• Traversal : Visit every part of the data structure
• Search : Traversal through the data structure for a given element
• Insertion : Adding new elements to the data structure
• Deletion : Removing an element from the data structure.
• Sorting : Rearranging the elements in some type of order(e.g Increasing or Decreasing)
• Merging : Combining two similar data structures into one

2.1 Arrays
An array is a kind of data structure that can store a fixed-size sequential collection of elements
of the same type in contiguous memory locations. It is used to store a collection of data, but it
is often more useful to think of an array as a collection of variables of the same type. Instead of
declaring individual variables, such as number0, number1, . . . , and number99, you declare one
array variable such as number and use number[0], number[1], and . . . , number[99] to represent
individual variables. A specific element in an array is accessed by an index. All arrays consist of
contiguous memory locations. The lowest address corresponds to the first element and the highest
address to the last element. Figure 2.1 exhibits the various kinds of arrays that we may come across.

2.1.1 Types of indexing in array:


• 0 (zero-based indexing): In this type of indexing, the first element of the array is indexed
by subscript of 0. Many languages including C and Java use this type of indexing. Now,
to understand why it is used in C, you may refer to https://developerinsider.co/
why-does-the-indexing-of-array-start-with-zero-in-c/.
• 1 (one-based indexing): The first element of the array is indexed by subscript of 1. Some
developers believe that starting array indexing at 1 is natural for counting. Hence, it is used
in languages like MATLAB, etc.
20 Chapter 2. Linear Data structures

Figure 2.1: Different types of arrays

• n (n-based indexing): The base index of an array can be freely chosen. Usually programming
languages allowing n-based indexing also allow negative index values and other scalar data
types like enumerations, or characters may be used as an array index. For example, in
Example 2.1, since the array index starts at 0, trying to access arr[−2] will give error as the
user is not permitted to access that memory location. However, in Example 2.2, the pointer p
refers to location arr[2] and the user is allowed to access arr[0] as it is within the scope of
the program. Hence, referring to p[−2] is actually referring to arr[0].
 Example 2.1
1 int arr[10];
2 int x = arr[-2]; // invalid; out of range

 Example 2.2
1 int arr[10];
2 int* p = &arr[2];
3 int x = p[-2]; // valid: accesses arr[0]

2.1.2 Methods for storing multidimensional arrays in linear storage


In computing, the methods for arranging multidimensional arrays in linear storage such as random
access memory are either row major and column major as shown in Figure 2.2.
• Row-major order: In row-major order, consecutive elements of the rows of the array are
contiguous in memory. This approach is usually followed in languages like C, C++, Python
and Java.
• Column-major order: In column-major order, consecutive elements of the columns are
contiguous in memory. This approach is usually followed in languages like Fortran and
MATLAB.
In matrix notation of array, the first index indicates the row, and the second index indicates the
column, e.g., a[1][2] is in the first row and in the second column. Even though the row is indicated
by the first index and the column by the second index, no grouping order between dimensions is
implied yet. This convention is carried over to two-dimensional arrays in computer languages,
although often with indexes starting at 0 instead of 1.
An obvious way to order objects with more than one attribute is to first group and order them
by one attribute, and then, within each such group, group and order them by another attribute, etc.
(not all attributes have to participate in ordering). If more than one attribute participate in ordering,
2.1 Arrays 21

Figure 2.2: Ordering of arrays

the first would be called major and the last minor. If two attributes participate in ordering, it is
sufficient to name only the major attribute.
Applied to two-dimensional arrays, e.g., for row-major order, all elements of the first row come
before all elements of the second row, etc., and, within each row, elements are ordered by column.
As one steps though consecutive memory locations, the index associated with the major order varies
slowest, and the index associated with the minor order varies fastest (unless there is only one index
value in that dimension).
 
a11 a12 a13
 Example 2.3 The array A = would be stored as shown in Figure 2.3 for row
a21 a22 a23
major and column major order.

Figure 2.3: Row major and Column major ordering

2.1.3 Array Cell Address Calculation


In a single dimensional array the address of an element of an array say A[i] is calculated using the
following formula 2.1 where, B is the base address of the array, W is the size of each element in
bytes, i is the subscript of element whose address is to be found and LB is the Lower limit / Lower
Bound of subscript (if not specified assume 0).
Addresso f A[i] = B +W × (i − LB) (2.1)
 Example 2.4 Given the base address of an array B[1300 . . . 1900] as 1020 and size of each

element is 2 bytes in the memory. Find the address of B[1700].


Solution: The given values are: B = 1020, LB = 1300,W = 2, i = 1700.
A[i] = B +W × (i − LB) = 1020 + 2 × (1700 − 1300) = 1020 + 2 × 400 = 1020 + 800 = 1820[Ans]

22 Chapter 2. Linear Data structures

In case of a two dimensional array, the address of an element of any array say A[i][ j] may
be calculated in 2 methods depending upon what type of ordering the array follows. In case of
Row Major System, the address of the location is calculated using equation 2.2 while in case of
Column Major System, the address of the location is calculated using equation 2.3 where, B =
Base address, i = Row subscript of element whose address is to be found, j = Column subscript
of element whose address is to be found, W = Storage Size of one element stored in the array (in
byte), Lr = Lower limit of row/start row index of matrix, if not given assume 0 (zero), Lc = Lower
limit of column/start column index of matrix, if not given assume 0 (zero), M = Number of row of
the given matrix, N = Number of column of the given matrix.

Addresso f A[i][ j] = B +W × [N × (i − Lr) + ( j − Lc)] (2.2)

Addresso f A[i][ j] = B +W × [(i − Lr) + M × ( j − Lc)] (2.3)

 Example 2.5 Consider a 30 X 4, 2D array with base address is 200 and 1 byte per memory
locations. Find out the address of A (15, 3).
Solution: B = 200, M = 30, N = 4, i = 15, j = 3,W = 1
a. Row Major
Address of A [15][3] = 200 + 1 [4 (15 - 1) + (3 - 1)] = 200 + 1 [56 + 2] = 200 + 58 =258
b. Column Major
Address of A [15][3]= 200 + 1 [30 (3 - 1) + (15 - 1)]= 200 + 1 [30 (2) + 14]= 200 + 74=274 

Another way of representing an array is by giving the lower limits of rows and columns as non-
zero values, e.g. A[Lr . . .Ur, Lc . . .Uc]. In such cases, the number of rows (M) will be calculated as
(Ur − Lr) + 1 while the number of columns (N) will be calculated as (Uc − Lc) + 1. Example 2.6
illustrates how to handle such cases.
 Example 2.6 An array X [-15 . . . 10, 15 . . . 40] requires 1 byte of storage. If beginning location
is 1500 determine the location of X [15][20].
Solution:
M = (Ur − Lr) + 1 = [10 − (−15)] + 1 = 26
N = (Uc − Lc) + 1 = [40 − 15)] + 1 = 26
B = 1500,W = 1, I = 15, J = 20, Lr = −15, Lc = 15, N = 26
Row-Major Ordering:
X[I][J] = B +W × [N × (I − Lr) + (J − Lc)] = 1500 + 1[26(15 − (−15))) + (20 − 15)]
= 1500 + 1[26 × 30 + 5] = 1500 + 1[780 + 5] = 2285[Ans]
Column-Major Ordering:
X[I][J] = B +W × [(I − Lr) + M × (J − Lc)] = 1500 + 1[(26 − (−15)) + 26(26 − 15)]
= 1500 + 1[41 + 26 × 11] = 1500 + 327 = 1827[Ans] 

Exercise 2.1 For example 2.6 find the address of the locations X[26][26] and X[10][40] for
both row major and column major order. 

Exercise 2.2 Use the knowledge acquired to devise an equation to find the address of cell of
a k-dimensional array considering the base address as B, Storage Size of one element stored
in the array as W (in byte), Lk as the lower limit of the kth dimension and N1 , N2 , . . . Nk be the
dimension of the array along the kth dimension. 
2.2 Sparse Matrix 23

2.2 Sparse Matrix


In computer programming, a matrix can be defined with a 2-dimensional array. Any array with m
columns and n rows represent a m × n matrix. There may be a situation in which a matrix contains
more number of ZERO values than NON-ZERO values (> 23 of the number of elements = 0 and < 31
of the number of elements is 6= 0). Such matrix is known as sparse matrix. A sample sparse matrix
is shown in Figure 2.4.The opposite of a sparse matrix is called a dense matrix.

Figure 2.4: Sparse Matrix

2.2.1 Is it efficient to use a 2-D array for a sparse matrix?


When a sparse matrix is represented with a 2-D array, we waste a lot of space to represent that
matrix. For example, consider a matrix of size 100 × 100 containing only 10 non-zero elements. In
this matrix, only 10 spaces are filled with non-zero values and remaining spaces of the matrix are
filled with zero. That means, totally we allocate 100 × 100 × 2 = 20000 bytes of space to store the
integer matrix and to access these 10 non-zero elements we have to make scanning for 10000 times.
To make it simple we represent a sparse matrix in different forms.

2.2.2 Sparse Matrix Representation


A sparse matrix can be represented by using arrays of smaller size by the following methods:
• Triplet Representation
• Compressed Sparse Row (CSR) or Yale Representation

2.2.3 Triplet Representation


In this representation, we consider only non-zero values along with their row and column index
values. In this representation, the 0th row stores the total number of rows, total number of columns
and the total number of non-zero values in the sparse matrix.
 Example 2.7 Consider a matrix of size 5 X 6 containing 6 number of non-zero values. This
matrix can be represented as shown in the Figure 2.5. 

Figure 2.5: Triplet Representation of a Sparse Matrix


24 Chapter 2. Linear Data structures

Algorithm 2.1 illustrates the process involved in transforming a sparse matrix to its triplet form.

Algorithm 2.1 Triplet Representation of sparse matrix


1: Read the no. of rows and columns of the original matrix as m and n
2: i ← 0, j ← 0, count ← 0
3: for i < m do
4: for j < n do
5: Read SP[i][ j]
6: if SP[i][ j] 6= 0 then
7: count ← count + 1
8: end if
9: end for
10: end for
11: Create T RIP[count + 1][3]
12: T RIP[0][0] ← m
13: T RIP[0][1] ← n
14: T RIP[0][2] ← count
15: k ← 1
16: for i < m do
17: for j < n do
18: if SP[i][ j] 6= 0 then
19: T RIP[k][0] ← i
20: T RIP[k][1] ← j
21: T RIP[k][2] ← SP[i][ j]
22: k ← k+1
23: end if
24: end for
25: end for

2.2.4 Compressed Sparse Row (CSR) or Yale Representation


The CSR (Compressed Sparse Row) or the Yale Format is similar to the Triplet Representation of
Sparse Matrix. We represent a matrix M (m × n), by three 1-D arrays or vectors called as A, IA, JA.
Let NNZ denote the number of non-zero elements in M and note that 0-based indexing is used.
• A is of size NNZ and stores the values of the non-zero elements of the matrix in the order of
traversing the matrix row-by-row
• IA vector is of size m + 1 stores the cumulative number of non-zero elements up to ( not
including) the ith row. It is defined by the recursive relation :
– IA[0] = 0
– IA[i] = IA[i − 1] + no of non-zero elements in the (i − 1)th row of the Matrix
• The JA vector stores the column index of each element in the A vector. Thus it is of size
NNZ as well.
Example 2.8 illustrates the conversion of a sparse matrix into its CSR format while algorithm
2.2 converts a sparse matrix into its CSR format.
 Example 2.8 As shown in Figure 2.6, the matrix A consists of all the non-zero elements of the
sparse matrix stored in a row wise order. The matrix JA stores the column number of each non-zero
element of the sparse matrix. Finally, the matrix IA has the first element as 0. The 2nd element is
the sum of the 1st element and the number of non-zero elements in the first row of the sparse matrix
2.2 Sparse Matrix 25

which is 2. Thus, the 2nd element is 0+2=2. The 3rd element is the sum of the 2nd element and the
number of non-zero elements in the 2nd row of the sparse matrix which is again 2. Hence, the 3rd
element is 2+2=4. Similarly, the rest of the elements are generated. 

Figure 2.6: CSR Representation of a Sparse Matrix

Algorithm 2.2 CSR Representation of sparse matrix


1: Read n and m
2: IA[0] ← 0
3: i ← 0, j ← 0, countA ← 0,countIA ← 0, countJA ← 0
4: for i < m do
5: rcount ← 0
6: for j < n do
7: Read SP[i][ j]
8: if SP[i][ j] 6= 0 then
9: A[countA] ← SP[i][ j]
10: JA[countJA] ← j
11: count ← count + 1
12: countA ← countA + 1
13: countJA ← countJA + 1
14: rcount ← rcount + 1
15: end if
16: end for
17: if i 6= 0 then
18: IA[i] ← IA[i − 1] + rcount
19: end if
20: end for
21: Display A, IA, JA

2.2.5 Addition of Sparse Matrices in Triplet Format


In order to add 2 sparse matrices A and B represented in the Triplet Format, firstly, the order of the
matrices needs to be checked. The elements in the first row and first 2 columns of the 2 matrices
should be equal, i.e., A[0][0] = B[0][0] and A[0][1] = B[0][1]. Now to add the matrices, we simply
traverse through both matrices row-wise, element by element and insert the smaller triplet (one with
smaller row and col value) into the resultant matrix. If we come across an element with the same
row and column value, we simply add their values and insert the added data into the resultant matrix.
Example 2.9 illustrates the addition of 2 sparse matrices in their triplet form while algorithm 2.3
provide the steps to add 2 sparse matrices in triplet form.
26 Chapter 2. Linear Data structures
   
0 0 9 0 0 0 0 3 0 0 0 0
 Example 2.9 Let the 2 sparse matrices be A = 0 0 0 0 0 0 and B = 0 0 0 0 0 0.
0 0 0 2 1 0 0 0 0 0 2 0
 
3 6 3  
0 2 9 3 6 2
Accordingly, their triplet representation will be Atrip = 2 3 2 and Btrip = 0 1 3 . From
  
2 4 2
2 4 1
the matrices Atrip and Btrip , it is clear that the only common position with non-zero element is
[2][4]. Thus, only the elements in Atrip [2][4] and Btrip [2][4] will be added in the resulting matrix
while all the other entries of the 2 matrices willbe simplycopied to the resultant matrix. Thus, the
3 6 4
0 2 9
 
intermediate resulting matrix will be SumT I =  2 3 2. Next we have to simply sort SumT I in

2 4 3
0 1 3
 
3 6 4
0 1 3
 
order to get the final matrix given by SumT F =  0 2 9
 
2 3 2
2 4 3

2.2.6 Product of Sparse Matrices in Triplet Format


To multiply the matrices A and B, we first generate transpose of the second matrix, i.e. B. Let us
call it BT . Next, sort BT and then we traverse both the matrices A and sorted BT row-wise while
comparing the 2nd column of each row of matrix A to the same of matrix BT . Say A[x][2] matches
with BT [y][2], the resultant matrix will append a triplet (x, y, A[x][2] × BT [y][2]) in the resulting
matrix. If multiple rows exist in the resulting matrix with same row and column value, add the 3rd
column corresponding to the entries and store them as 1 row in the matrix. Example 2.10 illustrates
the process.
   
4 4 5 4 4 5
1 2 10 1 3 8 
   
1 4 12 2 4 23
 Example 2.10 Let A =
3 3 5  and B = 3 3 9  be the 2 matrices in their triplet form.
   
   
4 1 15 4 1 20
4 2 12 4 2 25
 
  4 4 6
4 4 5 1
1 4 20  4 230
  1 1 200
T
2 4 25  
Then sorted B =   . The resulting matrix will be Prod = 
1 2 300. Please refer
3 1 8 

3 3 45 
3 3 9   
4 3 90 
4 2 23
4 4 276
to Figure 2.7-2.9 for details.


Exercise 2.3 Design an algorithm to take a sparse matrix in its triplet form as input and generate
the transpose of the matrix in the triplet form. 
2.2 Sparse Matrix 27

Algorithm 2.3 Addition of sparse matrices in Triplet Representation


1: Read Sparse matrices A and B in triplet format
2: Read number of rows of matrices A and B as alen and blen
3: if A[0][0] 6= B[0][0] or A[0][1] 6= B[0][1] then
4: Matrices cannot be added
5: else
6: apos ← 0, bpos ← 0
7: while apos < alen and bpos < blen do
8: if A[apos][0] > B[bpos][0]or (A[apos][0] = B[bpos][0] and A[apos][1] > B[bpos][1]) then
9: Insert the bposth row of matrix B in the SUM matrix
10: bpos ← bpos + 1
11: else if A[apos][0] < B[bpos][0] or (A[apos][0] = B[bpos][0] and A[apos][1] < B[bpos][1])
then
12: Insert the aposth row of matrix A in the SUM matrix
13: apos ← apos + 1
14: else
15: temp ← A[apos][2] + B[bpos][2]
16: if temp 6= 0 then
17: Insert the first 2 columns of the aposth row of matrix A in the SUM matrix
18: SUM[apos][2] ← temp
19: apos ← apos + 1
20: bpos ← bpos + 1
21: end if
22: end if
23: end while
24: while apos < alen do
25: Insert aposth row of A in SUM
26: apos ← apos + 1
27: end while
28: while bpos < blen do
29: Insert bposth row of B in SUM
30: bpos ← bpos + 1
31: end while
32: Sort SUM
33: Display SUM
34: end if
28 Chapter 2. Linear Data structures

Figure 2.7: Stepwise calculation of the product of 2 sparse matrices in triplet format-1
2.2 Sparse Matrix 29

Figure 2.8: Stepwise calculation of the product of 2 sparse matrices in triplet format-2

Figure 2.9: Stepwise calculation of the product of 2 sparse matrices in triplet format-3
30 Chapter 2. Linear Data structures

Exercise 2.4 Design an algorithm to take 2 sparse matrices in their triplet form as input and
generate the product of the matrix in the triplet form. 

Exercise 2.5 Design an algorithm to take a sparse matrix in its CSR form as input and generate
the transpose of the matrix in the CSR form. 

Exercise 2.6 Design an algorithm to take 2 sparse matrices in their CSR form as input and
generate the product of the matrix in the CSR form. 

2.3 Polynomial Representation using Array


A polynomial p(x) is the expression in variable x which is in the form (axn + bx( n − 1) + cx( n −
2) + jx + k), where a, b, c, . . . , k fall in the category of real numbers and n is non negative integer,
which is called the degree of polynomial. An essential characteristic of the polynomial is that each
term in the polynomial expression consists of two parts:
• one is the coefficient
• other is the exponent
 Example 2.11 Let us consider p(x) = 10x2 + 26x. Here, 10 and 26 are coefficients and 2, 1 is
its exponential value. 

2.3.1 Points to keep in Mind while working with Polynomials:


• The sign of each coefficient and exponent is stored within the coefficient and the exponent
itself
• The storage allocation for each term in the polynomial must be done in ascending and
descending order of their exponent
There may arise some situation where we need to evaluate many polynomial expressions and
perform basic arithmetic operations like addition and subtraction with those numbers. For this, we
need to represent the polynomials in the program. The simplest way to represent a polynomial is
by storing the coefficients of the terms of the polynomial in the cells whose index is same as the
exponent of the term. Example 2.12 illustrates the scenario.
 Example 2.12 Say, the polynomial is p(x) = 10x2 + 26x − 24. We can easily represent them in
an array A, where A[0]=-24 as the term -24 has 0 as the exponent of x, A[1] = 26 and A[2] = 10. 

However, the approach taken in example 2.12 will be very expensive in case we have a
polynomial like p1(x) = 10x1000 + 26x − 24. In this case, the number of terms is 3, so ideally an
array with 3 cells should be sufficient. But in this case, it will require a 1000 cell sized array.
Accordingly, another approach may be considered. We can consider a structure which can be
used to represent each term. Since a polynomial may have more than 1 terms, we can use an array
of such structure to represent the polynomial. So every array element will consist of two values:
• Coefficient
• Exponent
Now, in order to perform any kind of operation using the polynomials multiple such arrays
may be necessary. Example 2.13 illustrates a scenario where we use 2 such arrays to represent 2
polynomials and finally use a third array to store the sum.
 Example 2.13 Let us find the sum of 2 polynomials p1 and p2 where p1(x) = 3x3 − 2x + 10
and p2(x) = 2x2 + 7x − 15. In order to represent these polynomials we may require 2 array of
structure and a 3rd array of structure to store the sum of the 2 polynomials. Figure 2.10 illustrates
the scenario.
2.4 Abstract Data Type 31

Figure 2.10: Polynomial Representation using array

Can you think of any other space efficient way of finding the sum of the polynomials? Well
we can use a single polynomial to store the elements of both the polynomials. If we try to find
the sum of 2 polynomials A(x) and B(x) comprising of m and n terms respectively, the sum of the
polynomials will have maximum m + n terms in the worst case scenario. Hence, the algorithm
2.4 can be used to represent and find the sum of the polynomials. Figure 2.11 illustrates the
representation procedure.

Figure 2.11: Space Efficient Polynomial Representation using array

Exercise 2.7 Illustrate why the method discussed in Figure 2.11 is more space efficient com-
pared to the method illustrated in Figure 2.10. 

Exercise 2.8 Design a space efficient algorithm to find the product of 2 polynomials. 

2.4 Abstract Data Type


An abstract data type (ADT) is a mathematical model for data types where a data type is defined
by its behavior (semantics) from the point of view of a user of the data, specifically in terms of
possible values, possible operations on data of this type, and the behavior of these operations. This
contrasts with data structures, which are concrete representations of data (Concrete Data Type),
and are the point of view of an implementer, not a user. Examples of ADT include Lists, Stacks,
Queues, etc. and their properties are illustrated in Example 2.14.
 Example 2.14 A List is an ADT that contains elements of same type arranged in sequential
order (may or may not be contiguous) and the following operations can be performed on the list.
• get() : Return an element from the list at any given position
32 Chapter 2. Linear Data structures

Algorithm 2.4 Finding the sum of 2 polynomials


1: Define structure poly with float coe f and int exp
2: Generate an array term[100] of type poly
3: Read the number of polynomials as numpolynomials
4: i ← 0
5: j ← 0
6: while numpolynomials do
7: Read the number of terms as numterms
8: k←0
9: while numterms do
10: Read term[i].coe f and term[i].exp
11: i ← i+1
12: k ← k+1
13: numterms ← numterms − 1
14: end while
15: j ← j+1
16: k ← k−1
17: numpoly ← numpoly − 1
18: end while
19: i ← i − 1
20: j ← 0
21: for j < i do
22: k ← j+1
23: for k ≤ i do
24: if term[ j].coe f f > 0 then
25: if term[k].coe f f > 0 then
26: if term[ j].exp = term[k].exp then
27: term[ j].coe f f ← term[ j].coe f f + term[k].coe f f
28: term[k].coe f f ← 0
29: end if
30: end if
31: end if
32: k ← k+1
33: end for
34: j ← j+1
35: end for
36: j ← 0
37: for j ≤ i do
38: if term[ j].coe f f > 0 then
39: Display term[ j]
40: end if
41: j ← j+1
42: end for
2.5 Linked List 33

• insert() : Insert an element at any position of the list.


• remove() : Remove the first occurrence of any element from a non-empty list.
• removeAt() : Remove the element at a specified location from a non-empty list.
• replace() : Replace an element at any position by another element.
• size() : Return the number of elements in the list.
• isEmpty() : Return true if the list is empty, otherwise return false.
• isFull() : Return true if the list is full, otherwise return false.
A Stack is another ADT that contains elements of same type arranged in sequential order. All
operations takes place at a single end that is top of the stack and following operations can be
performed:
• push() : Insert an element at one end of the stack called top.
• pop() : Remove and return the element at the top of the stack, if it is not empty.
• peek() : Return the element at the top of the stack without removing it, if the stack is not
empty.
• size() : Return the number of elements in the stack.
• isEmpty() : Return true if the stack is empty, otherwise return false.
• isFull() : Return true if the stack is full, otherwise return false.
A Queue is another ADT that contains elements of same type arranged in sequential order.
Operations takes place at both ends, insertion is done at end and deletion is done at front. The
following operations can be performed:
• enqueue() : Insert an element at the end of the queue.
• dequeue() : Remove and return the first element of queue, if the queue is not empty.
• peek() : Return the element of the queue without removing it, if the queue is not empty.
• size() : Return the number of elements in the queue.
• isEmpty() : Return true if the queue is empty, otherwise return false.
• isFull() : Return true if the queue is full, otherwise return false.


The definitions of the ADTs do not specify how these ADTs will be represented and how the
operations will be carried out. There can be different ways to implement an ADT, for example,
the List ADT can be implemented using arrays, or singly linked list or doubly linked list. Similarly,
stack ADT and Queue ADT can be implemented using arrays or linked lists.

2.5 Linked List


A linked list is a linear data structure where each element is a separate object. It is a dynamic data
structure which implements the functionality of the ADT List. The number of nodes in a list is not
fixed and can grow and shrink on demand. Any application which has to deal with an unknown
number of objects will need to use a linked list. Figure 2.12 exhibits a Single Linked List (also
known as Simply Linked List).

Figure 2.12: Single Linked List

Each element (we will call it a node) of a list is comprising of two items-the data and a reference
34 Chapter 2. Linear Data structures

to the next node. The last node has a reference to null. The entry point into a linked list is called
the head of the list. It should be noted that head is not a separate node, but the reference to the first
node. If the list is empty then the head is a null reference.

Note 2.5.1 The pointer head is very very important and should never be used for list manipu-
lation or traversal. If we somehow loose the value of the starting node of the linked list (which
is stored in head), we will loose the list completely. Hence, head should never be used as the
L-value in case of linked list operations other than when it is initialized as null (cases when
there exists no node in the linked list). 

2.5.1 Advantages of a Linked List over Array


Arrays are static structures and therefore cannot be easily extended or reduced to fit the data set.
Hence, if we declare an array of size 1000 thinking that we can store sufficient number of elements,
it might happen that number of elements that are actually getting stored is 15, resulting in the
wastage of significant amount of memory. On the contrary, we may assign an array of size 10
thinking that it will be sufficient but in future the requirement mat be of size 100. In that case, the
size of the array will be insufficient.
Arrays are also expensive to maintain new insertions and deletions. In order to delete an
intermediate element in an array, all subsequent elements need to be left shifted. Similarly, if we
need to insert an element in an intermediate position of an already filled array, the elements of the
subsequent positions will have to be right shifted. These shifting operations are costly in terms of
the number of computation.

2.5.2 Disadvantages of a Linked List over Array


Linked Lists does not allow direct access to the individual elements. If we want to access a particular
item then we have to start at the head and follow the references until we get to that requisite item.
Linked Lists use more memory compared to an array- extra 4 bytes (on 32-bit CPU) are used by
each node to store a reference to the next node.

2.5.3 Self Referential Structure


Self referential structures are those structures that have one or more pointers which point to the
same type of structure as their member. In case of a Linked list, we need to define each node as
a unit (structure) and each node needs to hold the reference to the next node. Accordingly, the
structure should have an element which will be a pointer to its own self. Example 2.15 illustrates
the concept of a self referential structure.
Example 2.15
1 struct node {
2 int data;
3 struct node *next;
4 };

2.5.4 Linked List Operations


The common operations of a Linked List usually involves the following:
• Create the List
– Of n nodes
– User choice
• Display the List
2.5 Linked List 35

• Count the number of nodes in the List


• Add a node
– In the beginning
– In the end
– In the intermediate position
∗ At the nth position
∗ After the nth node
∗ After the node having value n
∗ Before the nth node
∗ Before the node having value n
• Delete a node
– The first node
– The last node
– An intermediate node
∗ The nth node
∗ The node after the node having value n
∗ The node before the node having value n
∗ The node having value n
For explanation, each operation on the linked list will be implemented and discussed in the form
of a function and we will consider that it is called from the main() function as per user choice. The
implementation of the functions of the Singly Linked List can be considered as given in example
2.29.

2.5.5 Creating a node

The function create_node() (given in Example 2.16) assigns memory to a node of type node and
returns the base address of the node to the calling function. It stores value within the data part of
the node and assigns NULL to the next part of the node. Figure 2.13 illustrates the scenario where the
function create_node() is called from a function using the statement head = create_node().
 Example 2.16
1 //creates a new node and returns the address of the node
2 struct node* create_node(){
3 struct node *temp;
4 temp = (struct node*)malloc(sizeof(struct node));
5 temp->next=NULL;
6 printf("\nEnter data: ");
7 scanf("%d",&temp->data);
8 return temp;
9 }

Figure 2.13: Creating a node.


36 Chapter 2. Linear Data structures

2.5.6 Creating a Singly Linked List


A Single Linked List can be created by creating multiple nodes subsequently and chaining them
together. Initially, it is considered that head = NULL. (If head 6= NULL, it implies that a list already
exists whose first node is pointed by head.) It can be created depending upon the user choice. The
user may choose to create a list comprising of n nodes while taking the value of n as input from the
user or may create the list one node at a time based on user choice. Example 2.17 illustrates the
function to create a Singly Linked List comprising of n nodes while taking n as input from the user.
 Example 2.17 Create a Singly Linked List comprising of n nodes.

1 struct node* create_list_n_nodes(struct node *head, int n){


2 struct node *change;
3 head = create_node();
4 //change points to the last node of the list. If list has only 1 node, it is
the first as well as the last node
5 change = head;
6 for(i=0;i<n-1;i++){
7 temp = create_node();
8 change->next = temp;
9 change = change->next;
10 }
11 }

Figure 2.14: Creating a Single Linked List

After the first node is created (using line no. 3 of example 2.17), the change pointer is initial-
ized with the address of the node using the statement change = head; (line no. 5). Figure2.14 (a)
illustrates the scenario after the initialization of change. Then depending upon the user’s choice,
the 3 statements (line nos. 7-9), temp = create_node(); change->next = temp;change =
change->next; are executed. The statement temp = create_node(); (line no. 7) is responsi-
ble for creating a new node and the address of the newly created node is stored in the pointer temp
2.5 Linked List 37

as shown in Figure 2.14 (b). The statement change->next = temp; creates the link between the
nodes pointed by change and temp as shown in Figure 2.14 (c). Finally, the statement change =
change->next; is responsible for shifting the pointer change to the newly created last node of
the list which is pointed by temp. Figure 2.14 (d) illustrates the scenario.
Another method of creating a single linked list is based on the user choice. Example 2.18
illustrates the code snippet. The idea is somewhat similar except for the fact that a node is appended
to the list only if the user wants. So, the user is asked to enter his choice after every iteration.
 Example 2.18 Create a Singly Linked List based on user choice.

1 struct node* create_list_user_choice(struct node *head){


2 int c;
3 //create the first node of the list
4 head = create_node();
5 //change points to the last node of the list. If list only 1 node, it is the
first as well as the last node
6 change = head;
7 printf("\nPress 1 to add more nodes, 0 otherwise: ");
8 scanf("%d",&c);
9 while(c){
10 temp = create_node();
11 change->next = temp;
12 change = change->next;
13 printf("\nPress 1 to add more nodes, 0 otherwise: ");
14 scanf("%d",&c);
15 }
16 }

Now, which of the above 2 methods to be used should also depend upon the user choice.
Accordingly, the function create_list() may be called from the main() function which in turn
should call the functions create_list_n_nodes() or create_list_user_choice depending
upon the user choice. Example 2.19 provides the code snippet for the function create_list().
 Example 2.19 Creating a Single Linked List.

1 struct node* create_list(struct node *head){


2 int ch,i,c,n;
3 struct node *temp, *change;
4 if(head!=NULL)
5 printf("List already exists");
6 else{
7 do{
8 printf("\n1. Create a list comprising of n nodes \n2. Create as
per user choice \nEnter choice");
9 scanf("%d",&ch);
10 if(ch<1||ch>2)
11 printf("\nWrong choice");
12 }while(ch<1||ch>2);
13 switch(ch){
14 case 1:
15 printf("\nEnter value of n: ");
16 scanf("%d",&n);
17 while(n<=0){
18 printf("\nInvalid number of nodes");
38 Chapter 2. Linear Data structures

19 printf("\nEnter value of n: ");


20 scanf("%d",&n);
21 }
22 head = create_list_n_nodes(head,n);
23 break;
24 case 2:
25 head = create_list_user_choice(head);
26 }
27 }
28 return head;
29 }

2.5.7 Displaying a Singly Linked List and Counting the number of nodes in the list
In order to display the content of each node or to count the number of nodes of a Single Linked List,
the entire list needs to be traversed. Accordingly, a pointer temp is initialized with the address of
the first node using the statement struct node *temp = head; in line no. 3 of the code snippet
given by Example 2.20. The loop statement while(temp!=NULL) and temp = temp->next; is
responsible for shifting the pointer temp to the end of the list while traversing the list. In each
iteration, one of the node of the list is processed. Accordingly, the counter count increments and
the statement printf("%d",temp->data); displays the content of the node.
 Example 2.20 Displaying the content of each node and counting the number of nodes of a Single
Linked List.
1 void display_list(struct node *head)
2 {
3 struct node *temp = head;
4 int count = 0;//for counting
5 if(head==NULL)
6 printf("\nList does not exist!!");
7 else{
8 printf("\n");
9 while(temp!=NULL){
10 count++;//for counting
11 printf("%d\t",temp->data);
12 temp = temp->next;
13 }
14 printf("\nCount = %d",count);//for counting
15 }
16 }

2.5.8 Inserting a new node in a Singly Linked List


In an existing single linked list, a new node may be inserted at any location. It can be beginning
(Refer Example 2.21), end (Refer Example 2.22) or any other intermediate position. In case of
intermediate position, the following cases may be considered:
• At the nth position (Refer Example 2.23)
• After the node having value n (Refer Example 2.24)
• Before the node having value n (Refer Example 2.25)
 Example 2.21 Inserting a new node at the beginning in a Singly Linked List.
2.5 Linked List 39

1 struct node* insert_beg(struct node *head){


2 struct node *temp = create_node();
3 temp->next = head;
4 head = temp;
5 return head;
6 }

The function struct node* insert_beg(struct node *head) is responsible for insert-
ing a node at the beginning of a single linked list whose first node is pointed by the pointer head.
The statement struct node *temp = create_node(); is responsible for creating a new node
pointed by temp as shown in Figure 2.15 (a). The statement temp->next = head; stores the
address held by head in the next part of the node pointed by temp indicating that the node pointed
by temp forms the predecessor node of the node pointed by head in the list. Figure 2.15 (b)
illustrates the scenario. Finally, set the head pointer over the node pointed by temp as shown in
Figure 2.15 (c).

Figure 2.15: Inserting a node at the beginning of a Single Linked List

 Example 2.22 Inserting a new node at the end of a Singly Linked List.

1 struct node* insert_end(struct node *head){


2 struct node *change;
3 struct node *temp = create_node();
4 change = head;
5 while(change->next!=NULL)//sets the pointer change on the last node of the SLL
6 change = change->next;
7 change->next = temp;
8 return head;
9 }

The function struct node* insert_end(struct node *head) is responsible for inserting a
40 Chapter 2. Linear Data structures

Figure 2.16: Inserting a node at the end of a Single Linked List

new node at the end of the single linked list. The statement struct node *temp = create_node();
calls the function create_node() to create a new node while the loop while(change->next!=NULL)
is responsible for setting the change pointer on the last node of the list. Finally, the new node is
connected to the last node of the list by storing the address of the new node, (held by temp) in the
next part of the last node as shown in Figure 2.16. 

 Example 2.23 Inserting a new node at the nth position in a Singly Linked List.

1 struct node* insert_at_nth_pos(struct node *head){


2 int n,i,flag;
3 struct node *change;
4 struct node *temp;
5 do{
6 flag=0;
7 printf("\nEnter n: ");
8 scanf("%d",&n);
9 if((n<0)||(n>count_nodes(head))){
10 flag = 1;
11 printf("\nInvalid value of n");
12 }
13 }while(flag);
14 change = head;
15 for(i=0;i<n-2;i++)//places the change pointer on the (n-1)th node
16 change = change->next;
17 temp = create_node();
18 temp->next = change->next;
19 change->next = temp;
20 return head;
21 }

In order to insert a new node at any intermediate position, the pointer change should be placed on
the node right after which the new node is to be inserted. Accordingly, in order to insert the new
node at the nth position, the change pointer should be placed on the (n − 1)th node. So, the change
pointer is initialized with head and is shifted (n − 2) times such that it places itself on the (n − 1)th
node. The loop for(i=0;i<n-2;i++) given in line no. 15 of Example 2.23 is responsible for
2.5 Linked List 41

shifting change (n-2) times. Parallely, the new node to be inserted has to be created. The statement
temp = create_node(); is responsible for creating the new node. The address of the new node
is held by the pointer temp as shown in Figure 2.17 (a). Now, the linking of the node pointed by
temp has to be done with the existing list. In order to do so, firstly, the address held in the next
part of the node pointed by change has to be stored in the next part of the node pointed by temp.
This is done by the statement temp->next = change->next; and is responsible for creating the
link between the new node and the list as shown in Figure 2.17 (b). Next, the address of the new
node has to be stored in the next part of the node pointed by change. This is done by the statement
change->next = temp;. Figure 2.17 (c) illustrates the effect.

Figure 2.17: Inserting a node at the nth position of a Single Linked List

 Example 2.24 Inserting a new node in a Single Linked List after a node having value n.

1 struct node* insert_after_node_value_n(struct node *head){


2 int n,i,chk=0;
3 struct node*change;
4 struct node *temp = create_node();
5 printf("\nEnter n: ");
6 scanf("%d",&n);
7 change = head;
8 while((change->data!=n)&&(change->next!=NULL))
9 change = change->next;
10 if(change->data==n){
11 temp->next = change->next;
12 change->next = temp;
13 }else
14 printf("Invalid value of n, n does not exist in the list");
42 Chapter 2. Linear Data structures

15 return head;
16 }

In order to insert a new node after a node having value n, first a new node has to be cre-
ated. The statement struct node *temp = create_node(); creates a new node whose ad-
dress is stored in the pointer temp. Next, the change pointer has to be shifted and placed on
the node having value n. The statement change = change->next; within the while loop
while((change->data!=n)&&(change->next!=NULL)) does the job. It shifts the change
pointer and sets it on the node having value n. Figure 2.18 (a) illustrates the scenario. Now, the
linking of the node pointed by temp has to be done with the existing list. This is done exactly like
the previous case. Figures 2.18 (b) and (c) illustrates the effect.

Figure 2.18: Inserting a node after a node having value n in a Single Linked List


2.5 Linked List 43

 Example 2.25 Insert a new node before a node having value n in a Singly Linked List.

1 struct node* insert_before_node_value_n(struct node *head){


2 int n,i,flag;
3 struct node *temp = create_node();
4 struct node*change;
5 printf("\nEnter n: ");
6 scanf("%d",&n);
7 change = head;
8 if (change->data==n)
9 head=insert_beg(head,temp);
10 else{
11 while((change->next->next!=NULL)&&(change->next->data!=n))
12 change = change->next;
13 if(change->next->data==n){
14 temp->next = change->next;
15 change->next = temp;
16 }else
17 printf("Invalid value of n, n does not exist in the list");
18 }
19 return head;
20 }

In order to insert a new node before a node having value n, first a new node has to be created.
The statement struct node *temp = create_node(); creates a new node whose address
is stored in the pointer temp as shown in Figure 2.19(a). Next, the change pointer has to be
shifted and placed on the node right before the node having value n. The statement change =
change->next; within the while loop given in line number 11 of Example 2.25 does the job.
Please note that unlike the previous case, in this case, we are checking the data stored in the next
node before progressing the change pointer. Once, the change pointer is placed in its appropriate
position, the linking of the node pointed by temp has to be done with the existing list. This is done
exactly like the previous case. Figures 2.19 (b) and (c) illustrates the effect.


 Example 2.26 Inserting a node in a Single Linked List.

1 struct node* insert_node(struct node *head)


2 {
3 int ch,c,n;
4 struct node *change;
5 if(head==NULL){
6 printf("\nList does not exist creating a new one");
7 head = temp;
8 }
9 else{
10 printf("1.Insert new node in the beginning\n2. Insert new node in the
end\n3. Insert new node in an intermediate position");
11 scanf("%d",&ch);
12 switch(ch){
13 case 1: head=insert_beg(head);
14 break;
15 case 2: head=insert_end(head);
16 break;
17 case 3: printf("\n1. Insert at the nth position\n2. Insert after the
nth node\n3. Insert after the node having value n\n4.Insert
44 Chapter 2. Linear Data structures

Figure 2.19: Inserting a node before a node having value n in a Single Linked List
2.5 Linked List 45

before the node having value n");


18 printf("\nEnter choice");
19 scanf("%d",&c);
20 while(c<1||c>4){
21 printf("\nWrong choice");
22 printf("\n1. Insert at the nth position\n2. Insert after
the nth node\n3. Insert after the node having value
n\n4.Insert before the node having value n");
23 printf("\nEnter choice: ");
24 scanf("%d",&c);
25 }
26 switch(c){
27 case 1: head=insert_at_nth_pos(head);
28 break;
29 case 2: head=insert_after_nth_node(head);
30 break;
31 case 3: head=insert_after_node_value_n(head);
32 break;
33 case 4: head=insert_before_node_value_n(head);
34 break;
35 }
36 }
37 }
38 return head;
39 }

An end-user may use any of the options to insert a node in a singly linked list. Hence, the
functions discussed in Examples 2.21-2.25 should be called from another function which will
call any of these functions depending upon the user choice. Accordingly, the function struct
node* insert_node(struct node *head) is called from the main() which in turn calls the
aforementioned functions. 

2.5.9 Deletion of nodes from a Singly Linked List


Firstly, I would like to say that, we cannot delete anything from the computer’s memory. We can
overwrite or dereference things from the computer’s memory. Hence, in the context of Linked List,
when we say deleting a node, we actually mean dereferencing it, i.e, the memory address of the
node will not be stored in any location within the stack memory of the computer. So, even if for
easy understanding we use the term ’deletion’, we actually mean dereferencing the node. In an
existing single linked list, a node located anywhere within the list may be dereferenced. It can be
the first node (Refer Example 2.27), the last node (Refer Example 2.28) or any other intermediate
node. In case of intermediate nodes, the following cases may be considered:
• Delete the nth node (Refer Example ??)
• Delete the node having value n (Refer Example ??)
• Delete the node after the node having value n (Refer Example ??)
• Delete the node before the node having value n (Refer Example ??)
 Example 2.27 Deleting the first node of a Singly Linked List

1 struct node* del_beg(struct node *head){


2 struct node *change = head;
3 head = head->next;
4 free(change);
5 return head;
6 }
46 Chapter 2. Linear Data structures

In order to delete the first node of a singly linked list, we first set the pointer change on the first
node of the list using the statement struct node *change = head;. The scenario is illustrated
in Figure 2.20 (a). Next, we advance the head pointer using the statement head = head->next;
such that it now holds the address of the second node of the list. Figure 2.20 (b) illustrates the
scenario. Finally, we execute the statement free(change);. On executing the statement, the link
between the variable change and the first node of the list will break as shown in Figure 2.20 (c).
Eventually, no variable in the stack memory will hold the address of the first node which will give
the essence that the node is lost in the memory. Since, the space occupied by the node will not
have any reference from the stack memory anymore, it will be treated as available memory to the
system’s garbage collector. The system may therefore allocate this memory to some other process
requesting for it.

Figure 2.20: Deleting the first node of a Single Linked List

 Example 2.28 Deleting the last node of a Singly Linked List

1 struct node* del_end(struct node *head){


2 struct node *change;
3 change = head;
2.5 Linked List 47

4 while(change->next->next!=NULL)
5 change = change->next;
6 free(change->next);
7 change->next = NULL;
8 return head;
9 }

Figure 2.21: Deleting the last node of a Single Linked List

In order to delete the last node of a singly linked list, the pointer change has to be set on the
last but one node of the list. The loop while(change->next->next!=NULL) is responsible for
shifting the pointer from the first node to the desired position. On reaching the desired position,
the statement free(change->next); is executed. This statement is responsible for breaking
the link between the last but one node and the last node (dereferencing the last node). Once, the
last node has been dereferenced, the node pointed by change now becomes the new last node of
the list. Hence, the address of the dereferenced node is replaced by NULL using the statement
change->next = NULL;.
 Example 2.29 Deleting the nth node of a singly linked list

1 struct node* del_at_nth_pos(struct node *head){


2 int n,i;
3 struct node *change,*temp;
4 printf("\nEnter n: ");
5 scanf("%d",&n);
6 if((n<1)||(n>count_nodes(head)))
48 Chapter 2. Linear Data structures

7 printf("\nInvalid n value");
8 else{
9 change = head;
10 for(i = 0 ;i<n-2;i++)
11 change = change->next;
12 temp = change->next;
13 change->next = change->next->next;
14 free(temp);
15 }
16 return head;
17 }

 Example 2.30 Single Linked List.

1
2
3
4
5

6
7 struct node* del_node_val_n(struct node *head){
8 int n,i;
9 struct node *change,*temp;
10 printf("\nEnter n: ");
11 scanf("%d",&n);
12 change = head;
13 if(change->data==n)
14 head = del_beg(head);
15 else{
16 while((change->next->data!=n)&&(change->next->next!=NULL))
17 change = change->next;
18 if(change->next->data == n)
19 {
20 temp = change->next;
21 change->next = change->next->next;
22 free(temp);
23 }
24 else
25 printf("Invalid value of n, n does not exist in the list");
26 }
27 return head;
28 }
29
30 struct node* del_after_node_val_n(struct node *head){
31 int n,i;
32 struct node *change,*temp;
33 printf("\nEnter n: ");
34 scanf("%d",&n);
35 change = head;
36 while((change->data!=n)&&(change->next!=NULL))
37 change = change->next;
38 if((change->data == n)&&(change->next!=NULL))
39 {
40 temp = change->next;
2.5 Linked List 49

41 change->next = change->next->next;
42 free(temp);
43 }
44 else
45 printf("Invalid value of n, n does not exist in the list");
46
47 return head;
48 }
49
50 struct node* del_before_node_val_n(struct node *head){
51 int n,i;
52 struct node *change,*temp;
53 printf("\nEnter n: ");
54 scanf("%d",&n);
55 change = head;
56 if(change->data == n)
57 printf("\n No node before node with value n");
58 else if(change->next->data==n)
59 head = del_beg(head);
60 else{
61 while((change->next->next->data!=n)&&(change->next->next->next!=NULL))
62 change = change->next;
63 if((change->next->next->data == n)&&(change->next->next->next!=NULL))
64 {
65 temp = change->next;
66 change->next = change->next->next;
67 free(temp);
68 }
69 else
70 printf("Invalid value of n, n does not exist in the list");
71 }
72 return head;
73 }
74
75 struct node* delete_node(struct node *head)
76 {
77 int ch,c,n;
78 if(head==NULL){
79 printf("\nList does not exist creating a new one");
80 }else{
81 printf("1.Delete first node\n2. Delete last node\n3. Delete an
intermediate node");
82 scanf("%d",&ch);
83 switch(ch){
84 case 1: head=del_beg(head);
85 break;
86 case 2: head=del_end(head);
87 break;
88 case 3: printf("\n1. Delete node at the nth position\n2. Delete the
node having value n\n3. Delete the node after the node having
value n\n4. Delete the node before the node having value n");
89 printf("\nEnter choice");
90 scanf("%d",&c);
91 while(c<1||c>4){
92 printf("\nWrong choice");
93 printf("\n1. Delete node at the nth position\n2. Delete
50 Chapter 2. Linear Data structures

the node having value n\n3. Delete the node after the
node having value n\n4. Delete the node before the
node having value n");
94 printf("\nEnter choice");
95 scanf("%d",&c);
96 }
97 switch(c){
98 case 1: head=del_at_nth_pos(head);
99 break;
100 case 2: head=del_node_val_n(head);
101 break;
102 case 3: head=del_after_node_val_n(head);
103 break;
104 case 4: head=del_before_node_val_n(head);
105 break;
106 }
107 }
108 }
109 }
110

111 int main()


112 {
113 struct node *head=NULL;
114 int ch,c,ct;
115 do{
116 printf("\n1. Create a singly linked list \n2. Display the list\n3. Count
the number of nodes in the list\n4. Insert a new node\n5. Delete a
node\n6. Exit\nEnter choice: ");
117 scanf("%d",&ch);
118 switch(ch){
119 case 1: head = create_list(head);
120 break;
121 case 2: display_list(head);
122 break;
123 case 3: ct=count_nodes(head);
124 break;
125 case 4: head = insert_node(head);
126 break;
127 case 5: head = delete_node(head);
128 break;
129 case 6: exit(1);
130 default: printf("Wrong choice!!");
131 }
132 }while(1);
133 }

2.5.10 Circular Singly Linked List


2.5.11 Doubly Linked List
2.5.12 Circular Doubly Linked List
2.6 Stacks
2.7 Queues
II
Module II
III
Module III
IV
Laboratory Assignments

3 Lab and Home Assignment Sheet . . . . 57


3.1 Introduction, Arrays, Linked Lists
3.2 Linear Data Structures
3.3 Non-linear Data Structures
3.4 Algorithms

Bibliography . . . . . . . . . . . . . . . . . . . . . . . . 63
3. Lab and Home Assignment Sheet

CSEN 2111: Data Structures and


Algorithms Lab
Name: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Roll: . . . . . . . . . . . . . . . . . . . . . . . .

Department of Computer Science and Engineering

Heritage Institute of Technology, Kolkata, INDIA

B.Tech 2nd Year, 2019

Instructors: Prof. Nilina Bera (NB), Prof. Adrija Bhattacharya (ADB),

Prof. (Dr.) Anindita Kundu (AK)

Technical Staffs: Mr. Surajit Acharya, Ms. Deepshikha Chowdhury


58 Chapter 3. Lab and Home Assignment Sheet

3.1 Introduction, Arrays, Linked Lists


3.1.1 Day 1: Time and Space Complexity
Lab Assignment:
• Create three 3x4 matrices matrixOne, matrixTwo and resultMatrix, using dynamic memory
allocation and store values in them. Multiply matrixOne and matrixTwo into resultMatrix
and display the resultMatrix.
• Instead of taking the values of each cells as input from the user, use the rand() and/or srand()
function, to store values in the matrices. Limit the values from 0 to 9. Multiply matrixOne
and matrixTwo into resultMatrix and display the resultMatrix.
• Increase the size of the matrices to 1000 x 1000 and perform the matrix multiplication. While
execution, open another terminal and use top command to see the usage of memory by the
process. Calculate the time taken for the execution of the program.
• Repeat the same exercise for the n x n matrices where n ∈ {10000, 100000, 10000000}.
• WAP to convert a sparse matrix into its triplet representation. Once represented in triplet
format, do not revert back to the matrix format anymore. Manipulate the triplet representation
to find the transpose of the matrix (which should also be in triplet representation). Calculate
and find out whether using triplet format for your example is advantageous or not.

Home Assignment:
• Write a program (WAP) to check whether a matrix is i) identity, ii) diagonal.
• WAP to reverse the elements of an array without using any other variable.
• WAP to add two matrices using triplet representation.

3.1.2 Day 2: Array


Lab Assignment:
• WAP to multiply two matrices using triplet representation.
• WAP to add two polynomials using array. Minimize the memory usage as much as you can.

Home Assignment:
• WAP to multiply two polynomials. Minimize usage of memory.
• WAP to convert a sparse matrix into CSR representation. Once represented in CSR format,
do not revert back to the matrix format anymore. Manipulate the CSR representation to find
the transpose of the matrix (which should also be in CSR representation). Calculate and find
out whether using CSR format for your example is advantageous or not.

3.1.3 Day 3: Single Linked List


Lab Assignment:
• Write a menu driven program to implement a singly linked list with the operations:
1. create the list
2. insert any element in any given position (front, end or intermediate)
3. delete an element from any given position (front, end or intermediate)
4. display the list

Home Assignment:
• Write a menu driven program to implement a singly linked list with the operations:
1. count the number of nodes
2. reverse the list
3. search an element in the list
3.2 Linear Data Structures 59

3.1.4 Day 4: Circular and Doubly Linked List


Lab Assignment:
• Write a menu driven program to implement a circular doubly linked list with the operations:
1. create the list
2. insert any element in any given position (front, end or intermediate)
3. delete an element from any given position (front, end or intermediate)
4. display the list

Home Assignment:
• Write a menu driven program to implement a circular doubly linked list with the operations:
1. count the number of nodes
2. reverse the list
3. search an element in the list

3.2 Linear Data Structures


3.2.1 Day 5: Stack, Queue - with array
Lab Assignment:
• Write a menu driven program to implement stack, using array, with
1. push
2. pop
3. display
4. exit
• WAP to evaluate a postfix expression.
• Write a menu driven program to implement a queue, using array, with
1. insert
2. delete
3. display
4. exit

Home Assignment:
• WAP to convert an infix expression to its corresponding postfix operation.
• Write a menu driven program to implement a double-ended queue, using array, with the
following operations:
1. insert (from front, from rear)
2. delete (from front, from rear)
3. display
4. exit operations

3.2.2 Day 6: Stack, Queue - with linked list


Lab Assignment:
• Write a menu driven program to implement a stack, using linked list, with
1. push
2. pop
3. display
4. exit

Home Assignment:
• Write a menu driven program to implement a queue, using linked list, with
1. insert
60 Chapter 3. Lab and Home Assignment Sheet

2. delete
3. display
4. exit

3.2.3 Day 7: Circular Queue, Deque - with linked list


Lab Assignment:
• Write a menu driven program to implement a circular queue using linked list with
1. insert
2. delete
3. display
4. exit

Home Assignment:
• Write a menu driven program to implement a double-ended queue, using linked list, with the
following operations:
1. insert (from front, from rear)
2. delete (from front, from rear)
3. display
4. exit operations

3.3 Non-linear Data Structures


3.3.1 Day 8: Binary Search Tree (BST)
Lab Assignment:
• Write a program, which creates a binary search tree (BST). Also write the functions to insert,
delete (all possible cases) and search elements from a BST.

Home Assignment:
• Write three functions to traverse and display the elements of a given BST in the following
orders using both recursive and iterative approaches:
1. in-order
2. pre-order
3. post-order

3.4 Algorithms
3.4.1 Day 9: Searching
Lab Assignment:
• WAP to implement a menu driven program for Linear Search and Binary Search (iterative).
As a pre-processing step, use bubble-sort to sort the elements in the search space.
• WAP to generate integers from 1 to n (input parameter) in random order and guarantee that
no number appears twice in the list. While the number sequence is being generated, store it
in a text file.

Home Assignment:
• WAP to implement binary search recursively.

3.4.2 Day 10: Sorting


Lab Assignment:
• Write different functions for implementing,
3.4 Algorithms 61

1. Bubble sort,
2. Cocktail shaker sort,
3. Quick Sort.
Plot a graph of n vs. time taken, for n = 100, 1000, 10, 000 and 100, 000 to compare the
performances of the sorting methods mentioned above. Use the second assignment of Day 9
to generate the data, using the given n values.
Home Assignment:
• Write different functions for implementing Insertion Sort and Merge Sort

3.4.3 Day 11: Graph Algorithms


Lab Assignment:
• Read a graph (consider it to be undirected) from an edge-list and store it in an adjacency list.
• Use the adjacency list to run DFS algorithm on the graph and print the node labels.
• Detect and count the back-edges.
Home Assignment:
• WAP to implement BFS algorithm of a given graph (similarly as described for DFS, instead
of back-edges count cross-edges).
Bibliography

You might also like