Data Structure 1
Data Structure 1
Chapter 1: Introduction
The field of computer science evolved around writing programs to solve several problems in a variety of
domains. A program is a combination of algorithms and data structures.
ℎ + =
Algorithms and data structures are two basic components that we have to clarify.
Algorithms:
Loosely speaking, an algorithm is a description of steps or instructions required to solve a given problem.
An algorithm also specifies the sequence in which the steps are to be performed. Once these steps
described in an algorithm are performed on a sample data representing an instance of the problem, the
expected result (which is also formally known as output data) is obtained.
Example; consider the problem of finding greatest common divisor (GCD) of two positive integers. The
inputs to an algorithm that solves the problem are two positive integers. The output is also a positive
integer which is the GCD of the integers given as input.
One famous algorithm, known as Euclid’s algorithm is described here to solve the problem.
Algorithm FindGCD
Step 5: Assign to
Step 6: Assign to .
Step 9: Print
While carrying out the steps of an algorithm on some input data, one must encounter some step containing
a statement like “STOP” after a finite number of steps.
Page | 1
Data Structures & Algorithms notes
“An algorithm is a sequence of instructions which act on some input data to produce some output within a
finite number of steps”
Characteristics/properties of algorithms:
(i) Input: There are some (possibly empty) input data which are externally supplied to the
algorithm.
(ii) Output: At least one output is the result.
(iii) Finiteness: if the instructions or steps of an algorithm are carried out then, for all possible
combinations of input data, the algorithm must terminate after a finite number of steps. An
algorithm is said to terminate if the step being carried out contains a statement like “HALT”
or “STOP”.
(iv) Definiteness: The steps must be clear and unambiguous.
(v) Effectiveness: The steps of an algorithm must be very basic. By “basic” , it is meant that if a
person wishes to carry out a step, one should be able to mechanically do that using a pencil
and a paper and without applying any intelligence. e.g following step is not basic;
Select a number so that the sum of its digits is equal to the product of the digits.
Algorithms & Procedures: While an algorithm must terminate in a finite number of steps, not all
programs terminate. The most typical example of an infinitely running program is the operating system. It
waits for events in an infinite loop and terminates only when the computer is switched off. Technically,
such programs are referred to as procedure. Otherwise, the words program and algorithm can be used
interchangeably.
Page | 2
Data Structures & Algorithms notes
Pseudocode is a detailed yet readable description of what a computer program or algorithm must do,
expressed in a formally-styled natural language rather than in a programming language. Pseudocode is
sometimes used as a detailed step in the process of developing a program. It allows designers to express
the design in great detail and provides programmers a detailed template for the next step of writing code
in a specific programming language. No standard for pseudocode syntax exists, as a program in
pseudocode is not an executable program.
mid := (right+left)/2
if a[mid] = value
return mid
right := mid-1
else
left := mid+1
end of while
return(-1)
Programming languages are probably the best languages to express algorithms as far as “definiteness”
property is concerned. But, syntax of a programming language often becomes a burden while designing
an algorithm as the focus trends to shift from designing to expressing the algorithm.
“type” or “data type” is a common terminology in mathematics and in programming languages. Variables
are declared to be of a particular “data type”. These declarations mean that the variables can assume
values only from a particular set. The type of the variables determines this set. Thus, “type” may be
defined as a set of values.
In programming languages like C, Pascal, the standard built-in types includes integers, real numbers,
characters and so on.
Variables declared of type integer may assume any integral value. However , depending on the machine,
there is a limit to the range of integers that can be properly used in any programming language.
A “type” is not a just a set of values. A “type” also determines the set of operations applicable to the set
of values that the said “type” represents.
Page | 3
Data Structures & Algorithms notes
e.g., for type ‘integers’, the operations are addition, multiplication, division, mod etc.
Types such as int, double, and char are called atomic types because we think of their values as single
entities only. Not something we wish to subdivide.
Computer programming languages like C, however provide tools such as arrays, structures, and pointers
with which we build new types, called structured types. A single value of a structured type is an array,
or file or linked list. A value of a structured type has two ingredients: It is made up of component
elements, and there is a structure, a set of rules for putting the components together.
Although most programming languages support some standard “data types”, they are not enough for most
applications. So, programming languages usually support creating new data types in terms of “structured
types”.
For our general point of view we shall use mathematical tools to provide the rules for building up
structured types. Among these tools are sets, sequences and functions.
For the study of lists the one that we need is the finite sequence which is defined recursively as:
“A sequence of length zero is empty. A sequence of length ≥ 1 of elements from a set T is an ordered
pair ( ,t), where is a sequence of length − 1 of elements from T, and t is an element of ”.
From this definition we can build up longer and longer sequences, starting with empty sequence and
adding on new elements from , one at a time.
Abstract Data Types: Definition of any abstract data type involves two parts: First is a description of the
way in which the components are related to each other, and second is a statement of operations that can be
performed on elements of the abstract data type.
Example: A List of elements of type is a finite sequence of elements of type together with the
operations
While defining the operations on an ADT, the aspect of implementation of the operation is not paid
attention to. After understanding the operations and the set of values for an ADT, one thinks of the
organization of the data of an ADT and then the implementation of the operations are considered. Further
note that the operations are only defined. The algorithms or procedures to carry out these operations are
not mentioned at all.
Page | 4
Data Structures & Algorithms notes
Data Structure: A data structure is merely an instance of of an ADT. Therefore, in order to define a data
structure, an ADT has to be defined.
The term data structure refers to the structural relationship present within data and thus should be viewed
as a 2-tuple ( , ) where “ ” is a finite set of nodes representing the data elements and “ ” is a set of
relationship among the nodes. For example, we may think of a tree data structure where nodes are related
to each other by “branching” relationship.
Analyzing an algorithm:
Once an algorithm is designed, there are two issues which are to be properly dealt with.
It is necessary to show that the designed algorithm yields correct output for all possible combinations of
input values. This is called “program proving” or “Program verification”.
The other aspect of analysis is evaluating the complexity of the program. When a program is executed, it
uses resources of a computing system such as CPU, memory etc. Most often there will be many different
algorithms for solving the same problem. A data structure can be represented in a number of ways and
there may be a number of algorithms to implement an operation on the said data structure> In such
situation, it is required to compare two algorithms and choose the better one. This is one of the most
important motivations to analyzing algorithm.
Analyzing an algorithm should typically reveal some quantitative metrices about requirement of
computing resources by the algorithm during its execution, would usually depend on the size of the input.
Thus time complexity of an algorithm is often expressed in terms of input size ( ).
Moreover the same algorithm may take different time to execute for different inputs having the same size.
The different types of time complexities that we interested in are
(i) the best case (ii) the worst case (iii) the average case.
Best Case time complexity of an algorithm is a measure of minimum time that will require for an input of
size “n”.
Worst Case time complexity of an algorithm is a measure of maximum time that an algorithm will
require for an input of size “n”.
Average Case time complexity is the time that an algorithm will require to execute a typical input data of
size “n”. Computation of average case time complexity of an algorithm requires some assumptions of
statistical distribution of the input data.
Time analysis implies defining a function ( ) that gives an estimate of the work done by an algorithm
for its execution on an input of size “n”.
Page | 5
Data Structures & Algorithms notes
An important step in the analysis of an algorithm is to identify the abstract operations on which an
algorithm is based.
Asymptotic Notations
Classifying functions by their asymptotic growth rates: We generally do not count all the steps executed
by an algorithm. But the total number of steps is roughly proportional to the number of basic operations
counted.
Suppose one algorithm does 2 basic operations, hence roughly 2 operations in total, for some
′
constant , and another algorithm does 4.5 basic operations or 4.5 in total. From those we can not tell
which one runs faster. The first algorithm may do more overhead calculations, i.e. its constant of
proportionality may be a lot higher. Thus if the functions describing the behavior of two algorithms differ
by a constant factor, it may be pointless to try distinguish between them. We consider such algorithms to
be in the same complexity class.
Now suppose one algorithm for a problem does multiplications and another algorithm does 5 , which
algorithm will run faster?
For smaller values of , the first does fewer multiplications, but for large values of , the second is better-
even if it does more overhead operations. The rate of growth of a cubic function is much greater than that
of a quadratic function. That is constant of proportionality does not matter when is large.
The examples suggest that there must be a way to compare of classifying functions that ignores constant
factors and small inputs. We get such a classification by studying “asymptotic growth rates” or
“asymptotic order” or simply the “order of” function.
Definition of - notation(Big-oh)
For all values of , to the right of , the value of the function ( ) is on or below ( ).
Page | 6
Data Structures & Algorithms notes
∴ ( ) = ( ( ))
. . , 3 + 2 = ( ), proved
We have ( ) = + +⋯……+ +
≤| | +| | + ⋯……+| | +| |
≤| | +| | + ⋯……+| | +| |
Let ( ) ∈ ( ( ))
( ) = (ℎ( )) proved
( )
( )
( )
↑
Page | 7
Data Structures & Algorithms notes
NOTE: This notation provides a way to define a partial order on functions and consequently on the
efficiency of different algorithms to solve a given problem. However the induced order is not total as
there exist functions , : → ∗ such that neither ( ) ∈ ( ( )) nor ( ) ∈ ( ( ))
NOTE: We have seen several functions ( ) and ( ) for which it is easy to prove that ( ) ∈ ( ( )).
How could we go about proving that a given function ( ) is not in order of another function? The
simplest way is to use a proof by contradiction.
Example: 10 +4 +2= ( )
∴ 10 +4 +2= ( ), proved
-notation: Ω notation produces an asymptotic lower bound. For a given function ( ) , we denote by
Ω( ( )) the set of functions
i.e. for all values of right to , the value of ( ) is on or above ( ) by a constant factor.
( )
( )
( )
↑
It is easy to see the ‘duality rule’. ( ) ∈ Ω(f(n)) if and only if ( ) ∈ ( ( )), because ( ) ∈ Ω(f(n))
implies ( ) ≥ ( ), or ( ) ≤ ( )≤ ( ) where = is a positive constant.
∴ ( ) ∈ ( ( ))
-notation: For a function ( ), we denote by Θ g(n) = {f(n): there exist positive constants c , c
and such that 0 ≤ ( )≤ ( )≤ ( ) for all ≥ }
Page | 8
Data Structures & Algorithms notes
i.e. the function ( ) belongs to the set Θ(g(n)) if there exists positive constants and such that it
can be ‘sandwiched’ between ( ) and ( ), for sufficiently large .
( )
( )
( )
( )
↑
Or ≤ − ≤ for all ≥
The R.H.S inequality can be made to hold for any value of ≥ 1 by choosing ≥ . Likewise, the left
hand inequality can be made to hold for any value of ≥ 7 by choosing ≤ . Thus by choosing =
, = and = 7, we can verify that − 3 = Θ(n )
Page | 9
Data Structures & Algorithms notes
SORTING
Sorting is an important operation and the need for sorting a list arises frequently in many applications.
Sorting is formally defined on a list of distinct elements , , … … … , if there is a binary relation
" < " defined for any two distinct elements and of that list. The list ( , , … … … , ) may be
arranged in ( !) ways. Sorting refers to one permutation ( , , … … … , ) of the list so that <
whenever < .
In many occasions, the list to be sorted is small enough so that the list can be entirely accommodated in
the primary memory of a computer. Sorting of such lists is referred to as internal sorting. As opposed to
this, the process of sorting a list stored in a secondary memory (such as hard disks) is known as external
sorting.
Usually, more than one sorting method is found to employ the same basic principle. This gives rise to
‘families’ of sorting methods. For example, there is a family of sorting methods known by the name
‘sorting by selection’. The basic principle used in these methods is as follows: At first, the element with
the smallest key is selected. The selected element is somehow discarded from the array and the smallest
of the remaining is selected next and so on. Obviously, the elements when placed in an output array in the
order of their selections form a sorted array.
The individual methods of the family differ in the matter of implementation details but the basic
principles are same.
Similarly, there are families like ‘sorting by insertion’, ‘sorting by exchange’, ‘sorting by merging’.
Another classification of sorting methods is based on the manner in which the keys are handled. Many
sorting methods work by comparing the key fields of the elements. The main concept in these methods is
to compare the keys of two elements to determine which of these two should be placed before the other in
the sorted array. These methods are known as comparative sorts. Most of the sorting techniques fall in
this category.
Bubble Sort
The basic idea behind the bubble sort is to pass through the file sequentially several times. Each pass
consists of comparing each element in the file with its successor(i.e [ ] with [ + 1]), and interchanging
the two elements if they are not in proper order. Consider the following file:
25 57 48 35 10 90 85 40
Page | 10
Data Structures & Algorithms notes
25 48 35 10 57 85 40 90
Notice that after the first pass, the largest (in this case 90) is in its proper position. In general [ − ] will
be in its proper position after iteration . This method is called ‘bubble sort’ because each number slowly
‘bubbles’ up to its proper position.
Original file: 25 57 48 35 10 90 85 40
Iteration 1: 25 48 35 10 57 85 40 90
Iteration 2: 25 35 10 48 57 40 85 90
Iteration 3: 25 10 35 48 57 40 85 90
Iteration 4: 10 25 35 40 48 57 85 90
Iteration 5: 10 25 35 40 48 57 85 90
Iteration 6: 10 25 35 40 48 57 85 90
Iteration 7: 10 25 35 40 48 57 85 90
Iteration 8: 10 25 35 40 48 57 85 90
: [1. . ]
: [1. . ]
ℎ : = 1 − 1
= 1 − 1
Page | 11
Data Structures & Algorithms notes
( [ ] > [ + 1])
= []
[ ] = [ + 1]
[ ] =
ENDIF
END FOR
END FOR
( [], )
, , ;
( = 0; < − 1; + +)
( = 0; < − − 1; + +)
( [ ] > [ + 1])
= [ ];
[ ] = [ + 1];
[ + 1] =
}/∗ ∗/
}/∗ ∗/
}/∗ ∗/
}/∗ ∗/
Each pass of this algorithm places an element in its proper position. Since all the elements positions
greater than or equal to ( − ) are already in position after iteration i, they need not be considered in
Page | 12
Data Structures & Algorithms notes
succeeding iterations. Thus on first pass ( − 1) comparisons are made, on the second pass ( − 2)
comparisons and so on. On the ( − 2)th pass only one comparison is made.
( − 1) passes are sufficient to sort a file of size . However in the previous example, the file was sorted
after five iterations, making the last two iterations unnecessary. To eliminate unnecessary passes we must
be able to detect the fact that file is already sorted. Since in a sorted file no further interchanges are made,
we can easily test whether or not any interchange are made in a given pass by keeping a Boolean variable
‘ ’.
Using this improvement, we can present a modified algorithm(C-function) for bubble sort:
2( [ ], )
, , ;
= ;
( = 0; ( < − 1)&&( == ); + +)
= ;
( = 0; < − − 1; + +)
( [ ] > [ + 1])
= [ ];
[ ] = [ + 1];
[ + 1] = ;
= ;
} /∗ ∗/
} /∗ ∗/
}/∗ 2 ∗/
Page | 13
Data Structures & Algorithms notes
Efficiency of BubbleSort: In case of algorithm1 for bubble sort, that does comparisons needed in th
iteration is ( − ). Thus iteration 1 needs ( − 1) comparisons. Thus total number of comparisons
needed
= ( − 1) + ( − 2) + ⋯ … . . . +2 + 1
( )
= = ( ).
Number of interchanges depends on the original order of the file. However, the number of interchanges
can not be greater than the number of comparisons.
The number of comparisons on iteration i is ( − ). Thus if there are iterations the total number of
comparisons is ( − 1) + ( − 2) + ⋯ … … + ( − ) which equals (2 − − )/2. It can be shown
that the average number of iterations , is ( ).
When the file is completely sorted and algorithm2 is used (i.e. a flag variable is used to check whether the
file is completely sorted or not). In this case, = 0 initially, the program enters inside the first ‘for’ loop,
sets = , then varies from 0 to ( − 2),i.e. ( − 1) times, but since the file is already sorted
no interchange is made and flag remains FALSE. So, in the next iteration, outer for loop terminates. Thus
number of comparisons is ( − 1) i.e. ( ).
Insertion Sort
The basic step in this method is to insert a record into a sequence of ordered records
, , ,……, (with ≤ ≤………≤ ) in such a way that the resulting sequence of ( + 1) is
also sorted.
In other words, on the ith pass we insert the th element [ ] into his rightful position among
[1], [2], … … . , [ − 1] which were previously placed in sorted order. After doing this, records
occupying [1], [2], … … . . , [ ] are in sorted order.
In insertion sort we pick up a particular element and then insert it at the appropriate place in the sorted
sublist, i.e. during th iteration, the element [ ] is placed in its proper position in the sorted sublist [1],
[2], … … … . , [ − 1]. This task is accomplished by comparing [ ] with [ − 1], [ − 2] and so on,
until the first element [ ] is found , such that [ ] ≤ [ ]. Then each of the elements [ +
1], … … , [ − 2], [ − 1] are moved one position up to the right, and then [ ] is inserted in ( + 1)th
position of the array.
Example Consider the following array A with 7 elements 35, 20, 40, 100, 3, 10, 15
35 20 40 100 3 10 15
20 35 40 100 3 10 15
Page | 14
Data Structures & Algorithms notes
Pass 2: Place [3] (i.e. 40) into its proper position by comparing it with [2].
20 35 40 100 3 10 15
Pass 3: Place [4] (i.e. 100) into its proper position by comparing it with [3]
20 35 40 100 3 10 15
Pass 4: Place [5] (i.e. 3)in its rightful position by comparing it with [4], [3], [2] [1]
3 20 35 40 100 10 15
Pass 5: Place [6] (i.e. 10) in its rightful position by comparing it with [5], [4], [3], [2] [1]
3 10 20 35 40 100 15
Pass 6: Place [7] (i.e. 15) in its rightful position by comparing it with
[6], [5], [4], [3], [2] and [1]
3 10 15 20 35 40 100
= [ ]
= −1
( < [ ] ≥ 1)
[ + 1] = [ ]
= −1
Page | 15
Data Structures & Algorithms notes
[ + 1] =
− :
( [ ], )
/∗ ℎ ℎ ∗/
( = 1; ≤ − 1; + +)
= [ ];
= − 1;
[ + 1] = [ ];
= − 1;
[ + 1] = ;
Analysis: (i) Best Case: If array A is in order then only one comparison will be made in the inner for
loop. Therefore the total number of comparisons made will be decided by the outer loop which are
( − 1).
(ii) Worst Case: If array A is in reverse order the inner loop must perform maximum number of
comparisons. First pass must perform only one comparison, the second pass requires ( − 2) comparisons
etc.
Page | 16
Data Structures & Algorithms notes
(iii) Average Case: on average, the number of comparisons in the inner loop = ( − 1)/2
( ) ( ) ( )
( )=∑ = + + … … … + = =( )
Characteristics:
(i) Insertion sort is suitable when is small. For large , it is very slow algorithm.
(ii) In the jth pass of insertion sort algorithm all elements from position 1 to − 1 are sorted. So
time may be saved by performing a binary search, rather than a linear search to find the
location in which the element [ ] is inserted in the sub-array [1], [2], … … … , [ − 1].
This requires comparisons rather than ( − 1) comparisons. However one still needs to
( )elements forward. Thus the order of complexity is not changed.
Shell Sort
This internal sorting technique makes repeated use of straight insertion sort or shuttle sort.
This method sorts separate subfiles of the original file. These sub files contain every kth element of the
original file. The value of k is called an increment. For example, if k is 5, the subfile consisting of
x[0],x[5],x[10],……… is first sorted. Five sub files each containing one fifth of the elements of the
original file are sorted this manner. These are
If a different increment is chosen, the subfiles are divided so that the ith element of the ℎ subfile is
[( − 1) ∗ + − 1].
After the first k sub-files are sorted 9usually by simple insertion), a new smaller value of is chosen and
the file is again partitioned into a new set of sub-files. Each of these larger sub files is sorted and the
process is repeated yet again with an even smaller value of . Eventually, the value of k is set to 1 so that
the subfile consisting of the entire file is sorted. A decreasing sequence of increments is fixed at the start
of the entire process. The last value in this sequence must be 1.
25 57 48 37 12 92 86 33
Page | 17
Data Structures & Algorithms notes
And the sequence (5,3,1) is chosen , the following subfiles are sorted on each iteration:
First iteration ( = 5)
( [0], [5])
( [1], [6])
( [2], [7])
( [3])
( [4])
Pass 1: =5
25 57 48 37 12 92 86 33
Pass 2: =2
25 57 48 37 12 92 86 33
Pass 3: =1
25 57 48 37 12 92 86 33
12 25 37 33 48 57 86 92
ℎ ( [ ], , [ ], )
, , , , ;
( = 0; < ; + +)
Page | 18
Data Structures & Algorithms notes
= [ ];
( = ; < ; + +)
/∗ [ ] ℎ ∗/
= [ ];
[ + ]= [ ]
[ + ]= ;
}/∗ ∗/
} /∗ ∗/
} /∗ ℎ ∗/
The idea behind the shell sort is a simple one. We know that simple insertion sort is highly efficient on a
file that is in almost sorted order. It is also noted that when the file size is small, an ( )is often more
efficient than an ( ) sort. The reason for this is that ( ) sorts are generally quite simple to
program and involve very few actions other than comparisons and replacements on each pass. Because of
this low overhead, the constant of proportionality is rather small. An ( ) sort is generally quite
complex and employs a large number of extra operations on each pass in order to reduce the work of
subsequent passes. Thus its constant of proportionality is simple. When is large, overwhelms
∗ , so that the constant of proportionality do not play a major role in determining the faster sort.
However when is small, is not much larger than ∗ , so that a large difference in those
constants often causes an ( ) sort to be faster.
Since the first increment used by the shell sort is large, the individual sub-files are quite small, so that the
simple insertion sorts on those sub-files are fairly small. Each sort of a sub-file causes the entire file to be
more nearly sorted. Thus although successive passes of the shell sort use smaller increments and therefore
deal with larger sub-files, those sub-files are almost sorted due to actions of previous passes.
Page | 19
Data Structures & Algorithms notes
Merge Sort
Merge sort is an example of divide and conquer algorithm. It has the property that in the worst case its
complexity is ( ). We assume that the elements are to be sorted in non-decreasing order.
Given a sequence of n elements [1], … … … , [ ] the general idea is to imagine them split into two sets
( [1], [2], … … … , [ ]) and ( + 1 , … … … , [ ]). Each set is individually sorted, and are merged to
produce a single sorted sequence of elements.
Before going into detail about the merge sort, we discuss about an algorithm ‘MERGE1’ which merge
two separate sorted arrays “A’ and ‘B’ of length and into a single sorted array C.
1( [ ], [ ], [ ], , )
{
= 0, = 0, = 0;
{ /∗ ℎ [] [] ∗/
( [ ] < [ ])
[ ] = [ ];
+ +;
+ +;
Page | 20
Data Structures & Algorithms notes
[ ] = [ ];
+ +;
+ +;
( >= ) /∗ ℎ ∗/
ℎ ( < )
[ ] = [ ];
+ +;
+ +;
( >= ) /∗ ℎ ∗/
ℎ ( < )
[ ] = [ ];
+ +;
+ +;
Page | 21
Data Structures & Algorithms notes
Here we have sonsidered two different sorted arrays ‘A’ and ‘B’ and merged into a single sorted array C.
Similarly, if we partition a single sorted array into subarrays and if these two subarrays are sorted, we can
merge them into a single sorted array.
( [ ], , ℎ ℎ)
( < ℎ ℎ)
( , , );
( , + 1, ℎ ℎ);
( , , , ℎ ℎ)
( [ ], , , ℎ ℎ)
, = + 1, = ;
[50];
( [ ] < [ ])
[ ] = [ ];
+ +;
+ +;
[ ] = [ ];
Page | 22
Data Structures & Algorithms notes
+ +;
+ +;
( >= ) /∗ ℎ ∗/
ℎ ( < )
[ ] = [ ];
+ +;
+ +;
( >= ) /∗ ℎ ℎ ∗/
ℎ ( < )
[ ] = [ ];
+ +;
+ +;
( = ; <= ℎ ℎ; + +)
[ ] = [ ];
Page | 23
Data Structures & Algorithms notes
We know that the comparisons to merge sorted arrays of size and is at most + − 1 i.e.
( + ).
So the time complexity for merging two sorted sub arrays of size /2 is + . ( ).
Merge sort algorithm divides the array into two equal sized sub arrays and then recursively sort them and
merge.
Thus the computing time for merge sort is described by the following equation
1 =1
( )=
2 + ( ) ≥1
2
Let us take O(n)=cn, where c is a constant and is some integral power of 2.i.e, =2 .
Now, ( ) = 2 +
( )=2 2 + +
4 2
=4 +2
4
=2 +2
2
= ⋯ … … … … ..
=2 +
2
= (1) + [ = 2 , = ]
= + [ (1) = = ]
( )= ( )
Example:
26 33 35 29 19 12 22
Page | 24
Data Structures & Algorithms notes
[12 19 22 26 29 33 35]
Quick Sort
Quick sort is based on divide and conquer paradigm. Here is the three step divide and conquer process for
sorting a typical subarray [ . . ].
Divide: The array [ . . ] is partitioned (rearranged) into two nonempty subarrays [ . . ] and
[ + 1. . ] such that each element of [ . . ] is smaller or equal to each element of [ + 1. . ]. The
index is computed as part of this partitioning procedure.
Conquer: The two subarrays [ . . ] and [ + 1. . ] are sorted by recursive calls to quicksort.
Combine: Since the subarrays are sorted in place, no work is needed to combine them. The entire
array is now sorted.
( , , )
( < )
← ( , , )
( , , )
( , + 1, )
Page | 25
Data Structures & Algorithms notes
Partitioning the array: The key to the algorithm is ‘partition’ procedure which rearranges the subarray
[ . . ] in place.
( , , )
← [ ]
← −1
← =1
ℎ ( )
← −1
( [ ]≤ )
← +1
( [ ]≥ )
( < )
ℎ ℎ ( [ ], [ ])
It selects an element = [ ] from the array [ . . ] as a ‘pivot’ element. It then grows two regions
[ . . ] and [ . . ] from the top and bottom of A[p..r], respectively such that every element in A[p..i] is
less than or equal to . Initially = − 1 and = + 1, so these regions are empty.
Within the body of the while loop, the index is decremented and index i is incremented until [ ] ≥ ≥
[ ]. Thus [ ] is too large to belong to the bottom region and A[j] is too small to belong to the top
region. Thus by interchanging [ ] and [ ], we can extend the two subregions.
Conceptually, the partitioning procedure performs a simple function. It puts elements smaller than into
the bottom of the array and elements larger than into the top region.
Page | 26
Data Structures & Algorithms notes
5 3 2 6 4 1 3 7
↑ ↑
pivot = [0] = 5
5 3 2 6 4 1 3 7
↑ j↑
Swap( [ ], [ ])
3 3 2 6 4 1 5 7
↑ ↑
Swap( [ ], [ ])
3 3 2 1 4 6 5 7
↑ ↑
Now > . So partition algorithm returns the position . So, we have the subarrays
3 3 2 1 4
← − − − − − − [ . . ] − − − − − −→ |
6 5 7
← − [ + 1. . ] − −→ |
1 2 3 3 4
1 2 3 3 4
1 2 3 3 4
1 2 3 3 4
Page | 27
Data Structures & Algorithms notes
Performance of QuickSort: The running time of quicksort depends on whether the partitioning
is balanced or unbalanced, and this in turn depends on which elements are used for partitioning. If the
partitioning is balanced, the algorithm runs asymptotically as fast as merge sort. If the partitioning is
unbalanced however, it can run asymptotically as slowly as insertion sort.
Worst case Analysis: the worst case behavior for the quicksort occurs when the partitioning routine
produces one subproblem of size (n-1) and one with a single element. Let us assume that this unbalanced
partitioning arises in each recursive call. The partitioning costs Θ( ) time. Since the recursive call on an
array of size one takes constant time, (1) = (1) and the recurrence equation for running time is
( ) = ( − 1) + (1) + Θ( )
, ( ) = ( − 1) + Θ( )
So, ( ) = ( − 2) + Θ( − 1) + Θ( )
…………………………………………
( ) = Θ(1) + Θ(2) + … … … … …. + Θ( )
T( ) = Θ( ( − 1)/2)
( ) = Θ( )
Best Case: The best case of quicksort occurs when the pivot we pick happens to divide the array into
two exactly equal parts, in every step. Thus we have two arrays each of size /2 for the original array of
size . This partitioning takes ( ) time.
Now, ( ) = 2 +
( )=2 2 + +
4 2
=4 +2
4
=2 +2
2
= … … … … ..
=2 +
2
= (1) + [ = 2 , = ]
Page | 28
Data Structures & Algorithms notes
′ ′
= + [ (1) = = ]
( )= ( )
Avergae Case : It can be shown that the average case time complexity of quick sort is also O( is some
integral power of 2.i.e, = 2 .
Now, ( ) = 2 +
( )=2 2 + +
4 2
=4 +2
4
=2 +2
2
= ⋯ … … … … ..
=2 +
2
= (1) + [ = 2 , = ]
′ ′
= + [ (1) = = ]
( )= ( )
Page | 29
Data Structures & Algorithms notes
Recursion
A powerful concept in the development of programs is the ability of a function to refer itself to solve
problems. This control technique, called recursion is an important concept in computer science and is
convenient for a variety of problems that would be difficult to solve using iterative constructs such as
for, while, do-while loops.
In mathematics and computer science, recursion means self reference. A recursive function therefore
is a function whose definition is based on itself. In other words a function containing either a call
statement to itself or a call statement to another function which may eventually result in a call
statement back to the original function is called a recursive function.
In order that it should not continue infinitely, a recursive function must have the following
properties:
(i) There must be certain condition, called the base criteria or terminating condition, for which
function should not call itself, i.e., the recursion stops.
(ii) Each time the function calls itself (directly or indirectly via another function), it must be
closer to the base criteria. That is it converges towards the terminating condition.
Recursion in C:
The C programming language allows a programmer to write subroutines and functions that call
themselves. Such a routines are called recursive.
( )
, ;
( = = 0)
(1);
= − 1;
= ( );
= ( ∗ );
Page | 30
Data Structures & Algorithms notes
In the statement = ( ); the function fact calls itself. This is the essential ingredient of a
recursive routine. The programmer assumes that the function being computed has already been
written and uses it in its own definition. However, the programmer must ensure that this does not
lead to an endless series of calls.
Consider the execution of this function when it is called by another function. For example suppose
that the above function is called from the ‘main’ by the statement
prrintf(“%d”,fact(4));
On the first needle were stacked 64 golden disks, each one slightly smaller than the one under it. The
priests were assigned the task of moving all the golden disks from the first needle to the second
(using third as temporary storage) subject to the condition that only one disk can be moved at a time,
and no disk is ever allowed to placed on top of a smaller disk. The priests were told that when they
had finished moving 64 disks, it would signify the end of the world.
Our task is to write a computer program that will print out the list of instructions for the priests. We
can generalize our task by writing a function ( , , , ) to move disks from needle to
needle using needle as temporary storage.
The solution: The problem is trivial if = 1. Then the disk is moved directly from needle to
needle . Assume that a solution to the problem of transferring ( − 1) disks from one needle to
another using a temporary needle is known to us. Then we can move top − 1 disks from needle
to needle using needle as a temporary. The largest radius disk which is remaining on needle
can now be directly moved to needle . Once this is done, the ( − 1) disk can be directly moved
from needle to needle using needle as temporary.
Note that the above description extends a solution for transferring n disks to a solution of n-1 disks.
Thus a recursive definition of the problem is obtained.
Page | 31
Data Structures & Algorithms notes
Let ( , , , ) denotes moving disks from needle ‘s’ to needle ‘d’ using ‘u’ as temporary
needle. Then Hanoi function can be defined as follows
( − 1, , , )
( , , , )= ℎ
( − 1, , , )
if >1
( , , , )
Call ( − 1, , , )
Call ( − 1, , , )
( , ℎ , ℎ , ℎ )
Page | 32
Data Structures & Algorithms notes
( = = 1)
(“ ℎ % % ”, , );
( − 1, , , );
(“ ℎ % % ”, , );
( − 1, , , );
Analysis: To analyze the time complexity of the function ‘Hanoi’ note that a call to ‘Hanoi’ results in
two calls to the same function. If T(n) denotes the no. of disk movements done by ‘Hanoi’ when it is
called with an actual parameter ‘ ’, then T9n0 can be written as,
( − 1) + 1 + ( − 1) > 0
T(n)=
1 = 1
2 ( − 1) + 1 > 0
i.e., T(n)=
1 = 1
Thus, ( ) = 2 ( − 1) + 1
or, ( ) = 2[2 ( − 2) + 1] + 1
or, ( ) = 2 ( − 2) + 2 + 1
or, ( ) = 2 (1) + 2 + … … … + 2 + 2 + 1
or, ( ) = 2 .1 +2 + … … … + 2 + 2 + 1
or, ( ) = 2 +2 + … … … + 2 + 2 + 1
or, ( ) = 2 − 1
Total number of disk movements in tower of Hanoi problem is 2 − 1, where = number of disks
Page | 33
Data Structures & Algorithms notes
(3, , , )
(2, , , )
(2, , , )
→
(1, , , ) → (1, , , )
(1, , , ) A→ (1, , , )
C→ →C
A→ B→
Backtracking:
8-queens problem: this is a very famous puzzle in which eight queens are to be placed on a 8 × 8
chessboard so that no two queen may attack each other. Note that a queen can attack another piece if
they lie on the same horizontal or vertical line or on the same diagonal in either direction.
The following diagram illustrates one configuration of the chessboard where eight queens are placed in
non-attacking positions. But this must be stated that this is not the only solution configuration for eight
queens problem.
Q
Q
Q
Q
Q
Q
Q
Q
Page | 34
Data Structures & Algorithms notes
A chessboard may be represented by a matrix (2D array) so that each cell in the chessboard may be
represented by an element of the matrix. All elements of the array may be initialized to zero. An element
of the array could be set to ‘1’ whenever a queen is placed on the corresponding cell.
That is, if ℎ [ ][ ] = 1 then a queen is placed on the jth column of the ith row of the chess board. It
can be easily seen that two queens can not be placed on the same row in a solution configuration,
because in that case, they would be in an attacking position with respect to each other. Thus, it is known
that each row in the array contains exactly one queen in a solution configuration. Let []=
denotes that the queen of the ith row is placed on the jth column.
Also we observe that every element on the same diagonal that goes from the upper left to the lower right
has the row-column value, examples are ℎ [3][1] and ℎ [5][3] etc.
Also every element on the same diagonal that goes from the upper right to the lower left has the same
+ value. Suppose two queens are placed at positions ( , ) and ( , ). Then by the above
they are on the same diagonal only if
− = − or = = + .
Therefore the two queens are on the same diagonal if and only if | − | = | − |
place ( , ) returns a Boolean value that is true if the kth queen can be placed in column i. it tests
whether is distinct from all previous values [1], … … … . , [ − 1] and whether there is
no other queen on the same diagonal.
Algorithm Nqueens(k,n)
⊳ This algorithm prints all possible placements n queens on × chessboard so that they are non-
attacking.
for : = 1 to do
if( ( , )) then
[ ]=
else ( + 1, )
end if
end if
end for
Page | 35
Data Structures & Algorithms notes
Algorithm ( , )
⊳ Returns true if a queen can be placed in kth row and ith column. Otherwise it returns false.
[ ] is a global array whose first ( − 1) values have been set
for = 1 to − 1do
return true;
end if
end for
Tail recursion: Suppose that the very last action of a function is to make a recursive call to itself. In
the stack implementation of recursion, the local variable of the function will be pushed onto the stack as
the recursive call is initiated. When the recursive call terminates, these local variables will be popped
from the stack and thereby restored to their former values. But doing so is pointless, because the recursive
call was the last action of the function, so that the just restored variables are immediately discarded.
When the very last action of a function is a recursive call to itself, it is thus pointless to use the stack,
since no variables need to be preserved. All that is needed is set the dummy calling parameters to their
new values and branch to the beginning of the function.
If the last executed statement of a function is a recursive call to itself, then this call can be eliminated by
changing the values of the calling parameters to those specified in the recursive call and repeating the
whole function.
M M M M
P P
P P
P P
Page | 36
Data Structures & Algorithms notes
Fig(a) shows the storage area used by the calling parameter M and several copes of the recursive
function P. Since each call by P to itself is its last action, there is no need to maintain the storage areas
after the call as shown in fig(b).
Finally fig(c) shows these calls to P as repeated iterative fashion on the same level.
M M
P P P
Fig (c)
This special case when a recursive call is the last executed statement of a function is known as tail
recursion.
By rearranging the termination condition if needed, it is possible to repeat the function using a while
statement or a do while statement or by using goto statement.
( , ℎ , ℎ , ℎ )
ℎ ;
ℎ ( > 0)
( − 1, , , );
(“ % % ”, , );
= − 1;
= ;
= ;
= ;
Page | 37
Data Structures & Algorithms notes
Or,
( , ℎ , ℎ , ℎ )
ℎ ;
( = = 1)
(“ % % ”, , );
( − 1, , , );
(“ % % ”, , );
= − 1;
= ;
= ;
= ;
Page | 38
Data Structures & Algorithms notes
Stack
Stack is one of the most important data structure commonly used in computer algorithms. Stack is a
special kind of linear list. A linear list of elements is usually denoted by = ( , , … … … , ),
where ′ ′ denotes the ith element of the list. Two operations that are frequently performed on a linear list
are insertion into and deletion from a list. In case of an ordinary linear list, these two operations may be
performed at any position in the list.
Definition: A stack is an ordered collection of elements into which new elements may be inserted and
from which elements may be deleted at one end called the top of the stack.
Given a stack = ( , , … … … , ), we may say that is the bottommost element, and element is
the element topmost element and element is said to be on the top of the element , 2 ≤ ≤ . Thus
if the elements are continuously inserted into a stack, then it grows at one end. And if elements are
deleted from a stack, it shrinks at the same end.
Unlike the array, the definition of the stack provides for the insertion and deletion of items, so that a stack
is dynamic, constantly changing. A single end of the stack at which items are inserted and also deleted, is
called top of the stack.
C
B B B
A A A A A
Fig above shows the expansion of an initial empty stack. Initially the stack is empty. As the elements
A,B,C are inserted the stack grows continuously upward.
Now if we remove the elements, then C will be deleted first, then B and finally A.
From the very nature of a stack, it is clear that the first element removed from a stack has been the last
element inserted into the stack. For example, in our example the first element deleted was C, which was
the last one inserted into the stack. For this reasons stacks are sometimes called LAST-IN-FIRST-OUT
structure or LIFO structure.
Abstract Definition: A stack can also be defined as an abstract data type (ADT) of type T is a
sequence of elements of type T together with the following operations.
Page | 39
Data Structures & Algorithms notes
‘Add’ and ‘Delete’ operations for a stack are known as ‘Push’ and ‘Pop’ operations respectively.
Implementation of Stack:
Linear implementation using array: the simplest way to represent a stack is by using one
dimensional array of size ‘MAXSIZE’, where ‘MAXSIZE’ is the maximum number of allowable
entries.
The first element is stored at stack[0], the second is stored at stack[1] and so on.
In addition to the one dimensional array ‘stack’ which is used to hold the elements of the stack,
another variable ‘top’ is required to keep track of the index of the last element inserted(i.e., the
topmost element);
Initially, when the stack is empty, ‘top’ has a value of −1 and when the stack contains a single
element, top has a value of zero and so on.
Each time an element is inserted, the pointer is increased by one, before the element is placed on the
stack.
Checking whether the stack is empty can be done by asking ‘ ( < 0)’
C declaration of stack: To implement a stack in a computer we shall set up an array that will hold the
items. In C we can make declarations such as following, where MAXSTACK is a symbolic constant
giving the maximum size allowed for stack and item-type is the type describing the data that will be
put into the stack.
item_type item[MAXSTACK];
int top;
};
Page | 40
Data Structures & Algorithms notes
Initialization of a stack in C:
( ∗ )
→ = −1;
ℎ ( , )
⊳ ℎ ℎ ℎ ℎ
⊳ , ‘ ’ ℎ ℎ
( ≥ − 1)
ℎ (“ ”);
← +1
[ ]←
ℎ( ∗ , )
( → == − 1)
(“ ”);
Page | 41
Data Structures & Algorithms notes
→ + 1;
→ [ ]= ;
Algorithm pop(S)
⊳ Removes the top element from a stack which is represented by a vector S and returns this element.
‘Top’ is a pointer to the topmost element of the stack
( == −1)
ℎ (‘ ”)
← −1
( [ + 1])
The possibility of underflow must be considered in implementing pop operation. Inadvertent attempt to
pop an element from an empty stack should be avoided.
( ∗ )
( ( ))
(“ ”);
Page | 42
Data Structures & Algorithms notes
(1);
= → [ → ];
→ = → − 1;
( );
Calling of pop function from main: The programmer can declare a variable i and write = (& );
( ∗ )
( → == −1)
(1);
(0);
Stacktop operation: This function returns the top element of a stack without removing it from the
stack.
( ∗ )
( ( ))
(“ ’);
(1);
else return(s→items[s→top]);
Page | 43
Data Structures & Algorithms notes
Application of Stacks:
Translation from infix to postfix notation:
There are two methods for converting an infix expression to its equivalent postfix expression.
Manual method; Steps needed to convert an infix expression to a postfix expression are
(ii) move all the operators so that they replace their corresponding right parentheses.
∗ −
When fully parenthesized, it yields (( ∗ ) − ). Now move all operators, so that they replace their
corresponding right parentheses.
( ∗ )−
∗ −
Example: Convert the following infix expression to its equivalent postfix expression
( + )∗( + )
Step1: fully parenthesize the expression and replace the right parentheses.
Step 2: (( + ) ∗ ( + ))
+ +∗
Page | 44
Data Structures & Algorithms notes
Push “(“ onto the stack, and add “)” to the end of the infix expression .
(a) repeatedly pop from stack and add to each operator on the top of the stack which has the
higher or same precedence than ‘op’.
(b) add ‘op’ onto the stack
end if
(a) repeatedly pop from stack and add to each operator. On the top of the stack, until a left
parenthesis is encountered.
(b) remove the left parenthesis(do not add the left parenthesis to ).
end if
end of while
end of Algorithm
Example: Use the postfix algorithm to convert the following expression into postfix form ∗ + ∗
Page | 45
Data Structures & Algorithms notes
Example: simulate algorithm infix-to-postfix to convert the following expression into its equivalent
postfix expression + ( ∗ − ( / ↑ ) ∗ ) ∗
Page | 46
Data Structures & Algorithms notes
(+ ∗ ∗ ↑/ ∗ −
) ∗ ↑/ ∗ − ∗ +
Example: convert the following infix expression ∗( + )∗ into the equivalent postfix form using
stack
Add ‘)’ at the end of the infix expression. Push ‘(‘ into the stack.
II. Evaluation of postfix expression: Each operator in a postfix string refers to the previous two operands
in the postfix string. These two operands may itself be the result of applying a previous operator. Now
suppose that each time we encounter an operand we push it onto a stack. When we reach an operator, its
operands will be top two elements, perform the indicated operation on them, and push the result on the
stack, so that it will be available for use as an operand of the next operator. The following algorithm
evaluates an expression in the postfix form.
ℎ _ ( : )
‘ ’
⊳ ℎ ℎ ‘ ’
ℎ ( ℎ )
( ) ⊳ ℎ ℎ
Page | 47
Data Structures & Algorithms notes
ℎ( , )
2= ( )
1= ( )
= 2 1
⊳ ℎ ℎ ℎ
ℎ( , )
( )
So the result is 41
Page | 48
Data Structures & Algorithms notes
Queue
A queue is an ordered collection of items from which items may be deleted at one end(called the front
end) and into which items may be inserted at the other end (called the rear end of the queue).
A B C
( )
Fig(i) represent a queue containing three elements A,B,C. Here A is at the ‘ ’ of the queue and C is at
the ‘ ’.
Fig(ii) represent the queue after an element has been deleted from the queue . So A is removed and B is
now at the front.
B C
( )
( . );
( , );
( , );
= ( );
( . );
( , );
( , );
B C D E F
( )
Page | 49
Data Structures & Algorithms notes
Implementation of queues:
1. Linear implementation: we can use an array to hold the elements of the queue and use two
integer variables, ‘front’ and ‘rear’ to hold the positions within the array of the first and last
elements of the queue.
A C-declaration for a queue:
# 100
{
[ ];
, ;
};
;
Using an array to hold a queue introduces the possibility of overflow, if the queue should grow larger than
the size of the array.
Ignoring the underflow and overflow for the moment, the operation ( , ) could be implemented
by the statements
. [+ + . ]= ;
During the initialization of a queue , its ‘ ’ and ‘ ’ are set to(−1) on each addition to a queue,
which logically takes place at the rear end of the queue , ‘ ’ is incremented by one. On the other hand,
after each removal operation, ‘ ’ is incremented by one. It has to be mentioned here that addition
must not be done if the queue is full and similarly removal should not be done if the queue is empty.
Therefore, conditions for empty queue and full queue have to be carefully formulated. The following
points have to be observed in this context.
(i) If the queue is non empty then ‘ ’ always denotes one less than the index of the
beginning of the queue.
(ii) The length of the queue is (‘ ’−‘ ’), and
(iii) If the condition (‘ ’ = = ’ ’) is TRUE the queue is empty.
( ∗ )
→ = → = −1;
( ∗ , )
Page | 50
Data Structures & Algorithms notes
( → == → )
(“ ! ”);
→ = → + 1;
= → [ → ];
( );
If insertion and deletions are implemented as above then a particular problem is encountered. Consider
the operations ‘addq’ and ‘deleteq’ being repeatedly performed on a queue in the sequence as shown in
fig.
= = −1
A B C D
= −1 =3
= =3
E F G H I J
=3 =9
Page | 51
Data Structures & Algorithms notes
Circular queue:
A more efficient queue representation using arrays is achieved by regarding the array of elements of a
queue to be circular. Assume that the index of array increases in clockwise direction except the index
(MAXQUEUE-1). Every element in the array has one clockwise and one anticlockwise neighbor. The
indices of the clockwise and the anticlockwise neighbors of the ith element of the array are ( +
1)% and ( − 1)% .
As before , ‘rear’ points to the element which is at the rear end of queue and ‘front’ points to the
anticlockwise neighbor of the beginning of the queue.
4
D
3
C
2
BB
Initially, let ‘ ’ and ‘ ’ initialized to zero. In this case = if and only if the is empty.
Above fig shows a circular queue with three elements, where front=1 and rear=4. Elements in this queue
are B,C and D.
Every insertion implies that ‘ ’ of the queue increases by 1, except the case when =
− 1. If rear is equal to ( − 1), then after one more insertion ‘ ’ is made
equal to zero provided the queue is not already
0 full.
The modified versions of the additions and deletion functions are given in the following:
=( → + 1)% ;
( == → ) /∗ ∗/
→ = ;
Page | 52
Data Structures & Algorithms notes
→ [ ]= ;
It is interesting to note that apparently the conditions for ‘queue empty’ and ‘queue full’ are the same.
But there is a subtle difference. During addition the condition ( → =q→ is checked after
the incrementing → . This further means that all the available MAXQUEUE number of
elements are not used in this implementation. In fact, the total capacity of the queue is now rendered
one less than MAXQUEUE. In order to use all of the MAXQUEUE entries, a special variable ‘count’
may be maintained for each queue to keep track of the number of elements in the queue and count
should be set to zero when a queue is initialized.
(1) _ ( ∗ )
{
→ = → = 0;
= 0;
} /∗ ∗/
(2) _ 2( ∗ , )
{
( == )
; /∗ ∗/
Page | 53
Data Structures & Algorithms notes
→ =( → + 1)% ;
→ [ → ]= ;
+ +;
(3) _ 2( ∗ )
{
;
( == 0)
{
(“ ”);
(−1);
}
{
→ =( → + 1)% ;
= → [ → ];
− −;
( );
Deques
A deque is a linear list in which items can be added or removed at either end but not in the middle.
The term deque is a contraction of the name double ended queue.
There are various ways of represting a deque in a computer. Unless it is otherwise stated or implied,
We will assume our queue is maintained by a circular array DEQUE with pointers LEFT and RIGHT,
which point to the two ends of the deque. We assume that the elements extend from the left end to the
right end of the array.
There are two variations of the deque- namely an input-restricted deque and an output-restricted
deque-which are intermediate between a deque and a queue. Specifically, an input restricted deque is
Page | 54
Data Structures & Algorithms notes
a deque which allows insertions at only one end of the list but allows deletions at both ends of the list;
and an output-restricted deque is a deque which allows insertions at both ends of the list but deletions
at only one end of the list.
Page | 55