Notes On Data Structures and Algorithms: Dr. Anindita Kundu
Notes On Data Structures and Algorithms: Dr. Anindita Kundu
Notes On Data Structures and Algorithms: Dr. Anindita Kundu
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
II Module II
IV Laboratory Assignments
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
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.
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.
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.
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.
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.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.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.
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]
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.
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.
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.
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.
• 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]
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.
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.
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
Algorithm 2.1 illustrates the process involved in transforming a sparse matrix to its triplet form.
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.
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
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.
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
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.
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.
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.
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).
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 }
36 Chapter 2. Linear Data structures
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.
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.
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 }
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).
Example 2.22 Inserting a new node at the end of a Singly Linked List.
The function struct node* insert_end(struct node *head) is responsible for inserting a
40 Chapter 2. Linear Data structures
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.
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.
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.
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.
Figure 2.19: Inserting a node before a node having value n in a Single Linked List
2.5 Linked List 45
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.
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.
4 while(change->next->next!=NULL)
5 change = change->next;
6 free(change->next);
7 change->next = NULL;
8 return head;
9 }
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
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 }
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
Bibliography . . . . . . . . . . . . . . . . . . . . . . . . 63
3. Lab and Home Assignment Sheet
Roll: . . . . . . . . . . . . . . . . . . . . . . . .
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.
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.
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
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
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
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
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
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.
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