1. Design & Analysis of
Algorithms
Syed Zaid Irshad
Lecturer, Department of Computer Science, MAJU
MS Software Engineering
BSc Computer System Engineering
2. Algorithm
• An Algorithm is a finite sequence of instructions, each of which has a clear meaning and can be
performed with a finite amount of effort in a finite length of time.
• No matter what the input values may be, an algorithm terminates after executing a finite number of
instructions.
• In addition, every algorithm must satisfy the following criteria:
• Input: there are zero or more quantities, which are externally supplied; Output: at least one
quantity is produced
• Definiteness: each instruction must be clear and unambiguous
• Finiteness: if we trace out the instructions of an algorithm, then for all cases the algorithm will
terminate after a finite number of steps
3. Algorithm
• Effectiveness: every instruction must be sufficiently basic that it can in principle be carried out by
a person using only pencil and paper.
4. Areas of Study of Algorithms
• How to devise algorithms?
• Techniques – Incremental, Divide & Conquer, Branch and Bound , Dynamic Programming, Greedy
Algorithms, Randomized Algorithm, Backtracking
• How to analyze algorithms?
• Analysis of Algorithms or performance analysis refer to the task of determining how much computing
time & storage an algorithm requires
• How to test a program?
• Debugging - Debugging is the process of executing programs on sample data sets to determine
whether faulty results occur and, if so, to correct them.
• Profiling or performance measurement is the process of executing a correct program on data sets and
measuring the time and space it takes to compute the results
5. Areas of Study of Algorithms
• How to validate algorithms?
• Check for Algorithm that it computes the correct answer for all possible legal inputs.
algorithm validation, First Phase.
• Second phase: Algorithm to Program, Program Proving or Program Verification
Solution be stated in two forms:
• First Form: Program which is annotated by a set of assertions about the input and
output variables of the program, predicate calculus
• Second form: is called a specification
6. Performance of Programs
• The performance of a program is the amount of computer memory and
time needed to run a program.
• Time Complexity
• Space Complexity
7. Time Complexity
• The time needed by an algorithm expressed as a function of the size of a
problem is called the time complexity of the algorithm.
• The time complexity of a program is the amount of computer time it needs
to run to completion.
• The limiting behavior of the complexity as size increases is called the
asymptotic time complexity.
• It is the asymptotic complexity of an algorithm, which ultimately determines
the size of problems that can be solved by the algorithm.
8. Space Complexity
• The space complexity of a program is the amount of memory it needs to
run to completion.
• The space need by a program has the following components:
• Instruction space: Instruction space is the space needed to store the compiled version
of the program instructions.
• The compiler used to complete the program into machine code.
• The compiler options in effect at the time of compilation
• The target computer.
9. Space Complexity
• The space need by a program has the following components:
• Data space: Data space is the space needed to store all constant and variable values.
Data space has two components:
• Space needed by constants and simple variables in program.
• Space needed by dynamically allocated objects such as arrays and class instances.
• Environment stack space: The environment stack is used to save information needed to
resume execution of partially completed functions.
10. Algorithm Design Goals
• The three basic design goals that one should strive for in a program are:
• Try to save Time
• A program that runs faster is a better program, so saving time is an obvious goal.
• Try to save Space
• A program that saves space over a competing program is considered desirable.
• Try to save Face
• By preventing the program from locking up or generating reams of garbled data.
11. Classification of Algorithms
• If “n” is the number of data items to be processed or degree of polynomial or the
size of the file to be sorted or searched or the number of nodes in a graph etc.
• 1
• Log n
• n
• n log n
• n^2
• n^3
• 2^n
12. Classification of Algorithms
• 1 (Constant/Best case)
• Next instructions of most programs are executed once or at most only a few times.
• If all the instructions of a program have this property,
• We say that its running time is a constant.
• Log n (Logarithmic/Divide ignore part)
• When the running time of a program is logarithmic, the program gets slightly slower as n grows.
• This running time commonly occurs in programs that solve a big problem by transforming it into a
smaller problem, cutting the size by some constant fraction.
• When n is a million, log n is a doubled. Whenever n doubles, log n increases by a constant, but log n
does not double until n increases to n^2.
13. Classification of Algorithms
• n (Linear/Examine each)
• When the running time of a program is linear, it is generally the case that a small
amount of processing is done on each input element.
• This is the optimal situation for an algorithm that must process n inputs.
• n log n (Linear logarithmic/Divide use all parts)
• This running time arises for algorithms that solve a problem by breaking it up into
smaller sub-problems, solving then independently, and then combining the solutions.
• When n doubles, the running time more than doubles.
14. Classification of Algorithms
• n^2 (Quadratic/Nested loops)
• When the running time of an algorithm is quadratic, it is practical for use only on relatively
small problems.
• Quadratic running times typically arise in algorithms that process all pairs of data items
(perhaps in a double nested loop) whenever n doubles, the running time increases four-fold.
• n^3 (Cubic/Nested loops)
• Similarly, an algorithm that process triples of data items (perhaps in a triple–nested loop) has
a cubic running time and is practical for use only on small problems.
• Whenever n doubles, the running time increases eight-fold.
15. Classification of Algorithms
• 2^n (Exponential/All subsets)
• Few algorithms with exponential running time are likely to be appropriate for practical
use, such algorithms arise naturally as “brute–force” solutions to problems.
• Whenever n doubles, the running time squares.
16. Complexity of Algorithms
• The complexity of an algorithm M is the function f(n) which gives the
running time and/or storage space requirement of the algorithm in terms of
the size “n” of the input data.
• Mostly, the storage space required by an algorithm is simply a multiple of the
data size “n”.
• Complexity shall refer to the running time of the algorithm.
17. Complexity of Algorithms
• The function f(n), gives the running time of an algorithm, depends not only
on the size “n” of the input data but also on the data.
• The complexity function f(n) for certain cases are:
• Best Case : The minimum possible value of f(n) is called the best case.
• Average Case : The expected value of f(n).
• Worst Case : The maximum value of f(n) for any key possible input.
18. Rate of Growth
• The following notations are commonly use notations in performance analysis
and used to characterize the complexity of an algorithm:
• Big–OH (O) (Upper Bound)
• The growth rate of f(n) is less than or equal (<) that of g(n).
• Big–OMEGA (Ω) (Lower Bound)
• The growth rate of f(n) is greater than or equal to (>) that of g(n).
• Big–THETA (ϴ) (Same Order)
• The growth rate of f(n) equals (=) the growth rate of g(n).
19. Rate of Growth
• Little–OH (o)
• 𝑛→∞
𝑓(𝑛)
𝑔(𝑛)
= 0
• The growth rate of f(n) is less than that of g(n).
• Little-OMEGA (ω)
• The growth rate of f(n) is greater than that of g(n).
25. Amortized Analysis
• In an amortized analysis, we average the time required to perform a sequence
of data structure operations over all the operations performed.
• With amortized analysis, we can show that the average cost of an operation
is small, if we average over a sequence of operations, even though a single
operation within the sequence might be expensive.
• Amortized analysis differs from average-case analysis in that probability is
not involved; an amortized analysis guarantees the average performance of
each operation in the worst case.
26. Amortized Analysis
• Three most common techniques used in amortized analysis:
• Aggregate Analysis
• Accounting method
• Potential method
27. Aggregate Analysis
• In which we determine an upper bound T(n) on the total cost of a sequence
of n operations.
• The average cost per operation is then T(n)/n.
• We take the average cost as the amortized cost of each operation .
28. Accounting method
• When there is more than one type of operation, each type of operation may
have a different amortized cost.
• The accounting method overcharges some operations early in the sequence,
storing the overcharge as “prepaid credit” on specific objects in the data
structure.
• Later in the sequence, the credit pays for operations that are charged less
than they cost.
29. Potential method
• The potential method maintains the credit as the “potential energy” of the
data structure instead of associating the credit with individual objects within
the data structure.
• The potential method, which is like the accounting method in that we
determine the amortized cost of each operation and may overcharge
operations early on to compensate for undercharges later.
30. The Rule of Sums
• Suppose that T1(n) and T2(n) are the running times of two programs fragments P1
and P2, and that T1(n) is O(f(n)) and T2(n) is O(g(n)).
• Then T1(n) + T2(n), the running time of P1 followed by P2 is O(max f(n), g(n)),
this is called as rule of sums.
• For example, suppose that we have three steps whose running times are respectively
O(n^2), O(n^3) and O(n. log n).
• Then the running time of the first two steps executed sequentially is O (max(n^2,
n^3)) which is O(n^3).
• The running time of all three together is O(max (n^3, n. log n)) which is O(n^3).
31. The rule of products
• If T1(n) and T2(n) are O(f(n)) and O(g(n)) respectively.
• Then T1(n)*T2(n) is O(f(n) g(n)).
• It follows term the product rule that O(c f(n)) means the same thing as
O(f(n)) if “c‟ is any positive constant.
• For example, O(n^2/2) is same as O(n^2).
32. The Running time of a program
• When solving a problem, we are faced with a choice among algorithms.
• The basis for this can be any one of the following:
• We would like an algorithm that is easy to understand, code and debug.
• We would like an algorithm that makes efficient use of the computer’s resources,
especially, one that runs as fast as possible.
33. Measuring the running time of a program
• The running time of a program depends on factors such as:
• The input to the program.
• The quality of code generated by the compiler used to create the object program.
• The nature and speed of the instructions on the machine used to execute the program,
and
• The time complexity of the algorithm underlying the program.
34. Asymptotic Analysis of Algorithms
• This approach is based on the asymptotic complexity measure.
• This means that we don't try to count the exact number of steps of a
program, but how that number grows with the size of the input to the
program.
• That gives us a measure that will work for different operating systems,
compilers and CPUs.
• The asymptotic complexity is written using big-O notation.
35. Rules for using big-O
• The most important property is that big-O gives an upper bound only.
• If an algorithm is O(n^2), it doesn't have to take n^2 steps (or a constant
multiple of n^2). But it can't take more than n2.
• So, any algorithm that is O(n), is also an O(n^2) algorithm. If this seems
confusing, think of big-O as being like "<".
• Any number that is < n is also < n^2.
36. Rules for using big-O
• Ignoring constant factors: O(c f(n)) = O(f(n)), where c is a constant; e.g., O(20 n^3) =
O(n^3)
• Ignoring smaller terms: If a<b then O(a+b) = O(b), for example O(n^2+n) = O(n^2)
• Upper bound only: If a<b then an O(a) algorithm is also an O(b) algorithm.
• n and log n are "bigger" than any constant, from an asymptotic view (that means for large
enough n). So, if k is a constant, an O(n + k) algorithm is also O(n), by ignoring smaller
terms. Similarly, an O(log n + k) algorithm is also O(log n).
• Another consequence of the last item is that an O(n log n + n) algorithm, which is O(n(log
n + 1)), can be simplified to O(n log n).
37. Properties of Asymptotic Notations
• 1. General Properties:
• If f(n) is O(g(n)) then a*f(n) is also O(g(n)); where a is a constant.
• Similarly, this property satisfies both Θ and Ω notation.
• 2. Transitive Properties:
• If f(n) is O(g(n)) and g(n) is O(h(n)) then f(n) = O(h(n))
• Similarly, this property satisfies both Θ and Ω notation.
• We can say
• If f(n) is Θ(g(n)) and g(n) is Θ(h(n)) then f(n) = Θ(h(n))
• If f(n) is Ω (g(n)) and g(n) is Ω (h(n)) then f(n) = Ω (h(n))
38. Properties of Asymptotic Notations
• 3. Reflexive Properties:
• Reflexive properties are always easy to understand after transitive.
• If f(n) is given, then f(n) is O(f(n)). Since MAXIMUM VALUE OF f(n) will be f(n) ITSELF!
• Hence x = f(n) and y = O(f(n) tie themselves in reflexive relation always.
• Example: f(n) = n²; O(n²) i.e., O(f(n))
• Similarly, this property satisfies both Θ and Ω notation.
• We can say that:
• If f(n) is given, then f(n) is Θ(f(n)).
• If f(n) is given, then f(n) is Ω (f(n)).
39. Properties of Asymptotic Notations
• 4. Symmetric Properties:
• If f(n) is Θ(g(n)) then g(n) is Θ(f(n)).
• Example: f(n) = n² and g(n) = n²
• then f(n) = Θ(n²) and g(n) = Θ(n²)
• This property only satisfies for Θ
notation.
• 5. Transpose Symmetric Properties:
• If f(n) is O(g(n)) then g(n) is Ω (f(n)).
• Example: f(n) = n, g(n) = n²
• then n is O(n²) and n² is Ω (n)
• This property only satisfies O and Ω
notations.
40. Properties of Asymptotic Notations
• 6. Some More Properties:
• If f(n) = O(g(n)) and f(n) = Ω(g(n))
then f(n) = Θ(g(n))
• If f(n) = O(g(n)) and d(n)=O(e(n))
• then f(n) + d(n) = O (max(g(n), e(n)))
• Example: f(n) = n i.e., O(n)
• d(n) = n² i.e., O(n²)
• then f(n) + d(n) = n + n² i.e., O(n²)
• If f(n)=O(g(n)) and d(n)=O(e(n))
• then f(n) * d(n) = O(g(n) * e(n))
• Example: f(n) = n i.e., O(n)
• d(n) = n² i.e., O(n²)
• then f(n) * d(n) = n * n² = n³ i.e., O(n³)
41. Calculating the running time of a program
• x = 3*y + 2;
• 5 n^3/100 n^2 = n/20
• for (i = 1; i<=n; i++)
v[i] = v[i] + 1;
• for (i = 1; i<=n; i++)
for (j = 1; j<=n; j++)
a[i,j] = b[i,j] * x;
• for (i = 1; i<=n; i++)
for (j = 1; j<=n; j++)
C[i, j] = 0;
for (k = 1; k<=n; k++)
C[i, j] = C[i, j] + A[i, k] *
B[k, j];
42. General rules for the analysis of programs
• The running time of each assignment read and write statement can usually be taken
to be O(1).
• The running time of a sequence of statements is determined by the sum rule.
• The running time of an if–statement is the cost of conditionally executed
statements, plus the time for evaluating the condition
• The time to execute a loop is the sum, over all times around the loop, the time to
execute the body and the time to evaluate the condition for termination.
43. Recurrence
• Many algorithms are recursive in nature.
• When we analyze them, we get a recurrence relation for time complexity.
• We get running time on an input of size n as a function of n and the running
time on inputs of smaller sizes.
• For example, in Merge Sort, to sort a given array, we divide it in two halves
and recursively repeat the process for the two halves.
44. Recurrence
• Time complexity of Merge Sort can be written as T(n) = 2T(n/2) + c*n.
• There are mainly three ways for solving recurrences.
• Substitution Method
• Recurrence Tree Method
• Master Method
• Iteration Method
45. Substitution Method
• One way to solve a divide-and-conquer recurrence equation is to use the iterative
substitution method.
• In using this method, we assume that the problem size n is fairly large and we than
substitute the general form of the recurrence for each occurrence of the function T on the
right-hand side.
• For example, consider the recurrence 𝑇(𝑛) = 2𝑇
𝒏
𝟐
+ 𝑛
• We guess the solution as 𝑇(𝑛) = 𝑂(𝑛 log 𝒏). Now we use induction to prove our guess.
• We need to prove that 𝑇(𝑛) <= 𝑐𝑛 log 𝒏. We can assume that it is true for values smaller
than n.
47. Recurrence Tree Method
• Another way of characterizing recurrence equations is to use the recursion
tree method.
• Like the substitution method, this technique uses repeated substitution to
solve a recurrence equation, but it differs from the iterative substitution
method in that, rather than being an algebraic approach, it is a visual
approach.
• 𝑇(𝑛) = 3𝑇
𝑛
4
+ 𝑐𝒏𝟐
48.
49. Master Method
• The master theorem is a formula for solving recurrences of the form
𝑇(𝑛) = 𝑎𝑇
𝑛
𝑏
+ 𝑓(𝑛), where 𝑎 ≥ 1 and 𝑏 > 1 and f(n) is
asymptotically positive.
• This recurrence describes an algorithm that divides a problem of size n into
a subproblems, each of size
𝑛
𝑏
, and solves them recursively.
50.
51. Master Method
• To find which case the T(n) belongs we can find the log𝑏 𝑎 and if the value
is:
• Greater than c it lies in case 1
• Equal to c it lies in case 2
• Less than c it lies in case 3
• Were c being the power of n in f(n)
Syed Zaid Irshad
56. Iteration Method
• The Iteration Method, is also known as the Iterative Method, Backwards
Substitution, Substitution Method, and Iterative Substitution.
• It is a technique or procedure in computational mathematics used to solve a
recurrence relation that uses an initial guess to generate a sequence of improving
approximate solutions for a class of problems, in which the nth approximation is
derived from the previous ones.
• A Closed-Form Solution is an equation that solves a given problem in terms of
functions and mathematical operations from a given generally-accepted set.
63. Incremental Technique
• An incremental algorithm is given a sequence of input and finds a sequence
of solutions that build incrementally while adapting to the changes in the
input.
• Insertion Sort
64. Insertion Sort
Iterate from arr[1] to
arr[n] over the array.
Compare the current
element (key) to its
predecessor.
If the key element is
smaller than its
predecessor, compare it
to the elements before.
Move the greater
elements one position up
to make space for the
swapped element.
Index 0 1 2 3 4 5 6 7 8 9
Element 4 3 2 10 12 1 5 6 7 9
65. Execution
Index 0 1 2 3 4 5 6 7 8 9
Element 4 3 2 10 12 1 5 6 7 9
Checking either index[1] is less than index[0]
Which is, in this case.
We than Swap the elements with each other.
Index 0 1 2 3 4 5 6 7 8 9
Element 3 4 2 10 12 1 5 6 7 9
66. Execution
Index 0 1 2 3 4 5 6 7 8 9
Element 3 4 2 10 12 1 5 6 7 9
Now checking either index[2] is less than index[1]
Which is, in this case.
So now we check if it is also index[2] is less than index[0]
Which is, in this case.
We than Swap the elements with each other.
Index 0 1 2 3 4 5 6 7 8 9
Element 2 3 4 10 12 1 5 6 7 9
67. Execution
Index 0 1 2 3 4 5 6 7 8 9
Element 2 3 4 10 12 1 5 6 7 9
Now checking either index[3] is less than index[2]
Which is not, in this case.
So now we skip it.
Index 0 1 2 3 4 5 6 7 8 9
Element 2 3 4 10 12 1 5 6 7 9
69. Algorithm Design
• INSERTION-SORT(index)
• for i = 1 to n
• key ← index [1]
• j ← 1 – 1
• while j > = 0 and index[j] > key
• index[j+1] ← index[j]
• j ← j – 1
• End while
• index[j+1] ← key
• End for
70. Algorithm Design
• INSERTION-SORT(index)
• for i = 1 to 9
• key ← 3
• j ← 0
• while 0 > = 0 and 4 > 3
• index[0+1] = index[1] ← 4
• j ← 0 – 1 = -1
• End while
• A[-1+1] = A[0] ← 3
• End for
Index 0 1 2 3 4 5 6 7 8 9
Element 4 3 2 10 12 1 5 6 7 9
For(i=1) 3 4 2 10 12 1 5 6 7 9
71. Algorithm Design
• INSERTION-SORT(index)
• for i = 1 to 9
• key ← 2
• j ← 1
• while 1 > = 0 and 4 > 2
• index[1+1] = index[2] ← 4
• j ← 1 – 1 = 0
• //End while
• //A[-1+1] = A[0] ← 3
• //End for
Index 0 1 2 3 4 5 6 7 8 9
Element 4 3 2 10 12 1 5 6 7 9
For(i=2) 2 3 4 10 12 1 5 6 7 9
INSERTION-SORT(index)
for i = 1 to 9
key ← 2
j ← 1
while 0 > = 0 and 3 > 2
index[0+1] = index[1] ← 3
j ← 0 – 1 = -1
End while
A[-1+1] = A[0] ← 2
End for
76. Properties
Time Complexity Big-O: O 𝑛2
, Big-Omega: Ω 𝑛 , Big-Theta: θ 𝑛2
Auxiliary Space O(1)
Boundary Cases
Insertion sort takes maximum time to sort if elements are sorted in reverse
order. And it takes minimum time (Order of n) when elements are already
sorted.
Algorithmic Paradigm Incremental Approach
Sorting In Place Yes
Stable Yes
Online Yes
Uses
Insertion sort is used when number of elements is small. It can also be
useful when input array is almost sorted, only few elements are misplaced in
complete big array.
77. Divide-and-Conquer approach
• A divide-and-conquer algorithm recursively breaks down a problem into two or more sub-
problems of the same or related type, until these become simple enough to be solved directly.
• The solutions to the sub-problems are then combined to give a solution to the original problem.
• Merge Sort
• A typical Divide and Conquer algorithm solves a problem using the following three steps.
• Divide: Break the given problem into subproblems of same type. This step involves breaking the problem into
smaller sub-problems.
• Conquer: Recursively solve these sub-problems.
• Combine: Appropriately combine the answers.
78. Merge Sort
Divide array
into two parts
Sort each part
of array
Combine
results into
single array
Index 0 1 2 3 4 5 6 7 8 9
Element 4 3 2 10 12 1 5 6 7 9
83. Algorithm Analysis
• Best/Worst/Average Case:
• Array division: log 𝑛
• Number of steps: log(𝑛 + 1)
• Middle point: 𝑂(1)
• Merge required: 𝑂(𝑛)
• Now by multiplying: n log(𝑛 + 1) = 𝑛 𝑙𝑜𝑔 𝑛 = 𝑂(𝑛 log 𝑛)
84. Properties
Time Complexity 𝑂(𝑛𝐿𝑜𝑔𝑛)
Auxiliary Space 𝑂(𝑛)
Boundary Cases
Algorithmic Paradigm Divide and Conquer
Sorting In Place No
Stable Yes
Online
Uses Merge Sort is useful for sorting linked lists in 𝑂(𝑛𝐿𝑜𝑔𝑛) time.
85. Heap Sort
• Heap
• Data Structure that manages information
• Array represented as a Near Complete Binary Tree: Each level, except possibly the
last, is filled, and all nodes in the last level are as far left as possible
• A.length and A.heap-size
97. Algorithm Analysis
• Worst Case occurs when the last level of the Heap is exactly (or atleast) half-full:
• Heap is a Near Complete Binary Tree (left sub-tree of any node is always larger or equal
in size than its right sub-tree)
• In the Worst Case, recursion would take place as many times as possible, which is only
possible if atleast the left sub-tree is completely filled
• In order to find an upper bound on the size of the sub-trees (maximum number of times
that recursion would take place), we need to observe the maximum size of the left sub-tree
only
• Proof
102. Quick Sort
• Efficient algorithm for sorting many elements via comparisons
• Divide-and-Conquer approach
103. Quick Sort
• It picks an element as pivot and partitions the given array around
the picked pivot. There are many different versions of quicksort that
pick pivot in different ways.
1.Always pick first element as pivot.
2.Always pick last element as pivot (implemented below)
3.Pick a random element as pivot.
4.Pick median as pivot.
104. Algorithm Design
• To sort an entire Array, the initial call is
• QUICKSORT (𝑨, 𝟏, 𝑨. 𝒍𝒆𝒏𝒈𝒕𝒉)
114. Algorithm Analysis
• Worst Case:
• The worst case occurs when the partition process always picks greatest or smallest element as
pivot.
• If we consider above partition strategy where last element is always picked as pivot, the worst
case will occur when the array is already sorted in increasing or decreasing order.
• Following is recurrence for worst case.
• T(n) = T(0) + T(n-1) + theta(n)
• which is equivalent to
• T(n) = T(n-1) + theta(n)
• The solution of above recurrence is theta (n^2).
115. Algorithm Analysis
• Best Case:
• The best case occurs when the partition process always picks the middle element as
pivot. Following is recurrence for best case.
• T(n) = 2T(n/2) + theta(n)
• The solution of above recurrence is theta (nLogn).
• It can be solved using case 2 of Master Theorem.
116. Algorithm Analysis
• Average Case:
• To do average case analysis, we need to consider all possible permutation of array and
calculate time taken by every permutation which doesn’t look easy.
• We can get an idea of average case by considering the case when partition puts O(n/9)
elements in one set and O(9n/10) elements in other set. Following is recurrence for this
case.
• T(n) = T(n/9) + T(9n/10) + theta(n)
• Solution of above recurrence is also O(nLogn)
119. Counting Sort
• Assumptions:
• Each of the 𝑛 input elements is an integer in the range: 0 𝑡𝑜 𝑘, where 𝑘 is an integer
• When 𝑘 = 𝑂(𝑛), 𝑻(𝒏) = 𝜣(𝒏)
• Determines for each input element 𝑥, the number of elements less than 𝑥
• Places element 𝑥 into correct position in array
• External Arrays required:
• 𝐵[1 … 𝑛]: Sorted Output
• 𝐶[0 … 𝑘]: Temporary Storage
129. Radix Sort
• Assumptions:
• Each of the 𝑛 input elements is a (maximum) 𝑑 − 𝑑𝑖𝑔𝑖𝑡 integer in the range: 0 𝑡𝑜 𝑘, where 𝑘 is an
integer
• When 𝑑 ≪ 𝑛, 𝑻(𝒏) = 𝜣(𝒏)
• Sorts recursively on each digit column, starting from the Least Significant digit
• Requires 𝑑 passes to sort all elements
• Application:
• Sort records using multiple fields
132. Algorithm Analysis
• General Case
𝑇 𝑛 = 𝑐1 𝑑 + 1 + 𝛩 𝑛 ∗ 𝑑
= 𝑑𝑐1 + 𝑐1 + 𝑑𝛩 𝑛
= 𝛩 𝑛 , 𝑖𝑓 𝑑 ≪ 𝑛 (which is true since 𝑑 ≪ 𝑘 for Radix Sort, and 𝑘 ≤ 𝑛 for Counting Sort)
𝑇(𝑛) = 𝛩(𝑛) (based on Counting Sort; see Table for other fields too)
• Best Case, Worst Case, or Average Case?
cost times
𝑐1 𝑑 + 1
𝛩(𝑛) 𝑑
133. Bucket Sort
• Assumptions:
• Input is drawn from a Uniform distribution
• Input is distributed uniformly and independently over the interval [0, 1]
• Divides the [0, 1) half-open interval into 𝑛 equal-sized sub-intervals or Buckets and distributes 𝑛 keys into
Buckets
• Bucket 𝑖 holds values in the interval [𝑖 𝑛 , 𝑖 + 1 𝑛)
• Sorts the keys in each Bucket in order
• External Array required:
• 𝐵[0 … 𝑛 − 1] of Linked Lists (Buckets): Temporary Storage
• Without assumption of Uniform distribution, Bucket Sort may still run in Linear time if
𝑖=0
𝑛−1
𝛩 𝐸 𝑛𝑖2 = 𝛩(1)
147. Algorithm Analysis
• Best Case: 𝑇 𝑛 = 𝛩(𝑛)
i. Each Bucket contains exactly one element
ii. One Bucket contains all elements in ascending order
• Average Case: 𝑇 𝑛 = 𝛩(𝑛2)
i. One Bucket contains all elements in non-ascending order
ii. Each Bucket contains from 2 to 𝑛 − 2 elements in ascending order
• Worst Case: 𝑇 𝑛 = 𝛩(𝑛3
)
i. Each Bucket contains from 2 to 𝑛 − 2 elements in non-ascending order
148. Comparison
Name Best Case Average Case Worst Case Space Complexity
Insertion O(n) O(n^2) O(n^2) O(n)
Merge O(n log n) O(n log n) O(n log n) O(n)
Heap O(n log n) O(n log n) O(n log n) O(n)
Quick O(log n) O(n log n) O(n^2) O(n+log n)
Counting O(n) O(n) O(n) O(n)
Radix O(n) O(n) O(n)
Bucket O(n) O(n^2) O(n^3)
149. Dynamic Programming
• “Programming” refers to a tabular method, and not computer code
• Application:
• Optimization Problems: Multiple solutions might exist, out of which “an” instead of “the”
optimal solution is acquired
• Sorting: Optimization Problem?
• Core Components of Optimization Problem for Dynamic Programming to be
applied upon:
1. Optimal Substructure
2. Overlapping Sub-problems
150. Dynamic Programming
1. Optimal Substructure: Optimal solution(s) to a problem incorporate
optimal solutions to related sub-problems, which we may solve
independently
• How to discover Optimal Substructure in a problem:
i. Show that a solution to the problem consists of making a choice
ii. Suppose that an optimal solution to the problem consists of making a choice
iii. Determine which sub-problems result due to Step 2
iv. Show that solutions to the sub-problems used within an optimal solution to the problem
must themselves be optimal
151. Dynamic Programming
• Optimal Substructure varies in two ways:
i. How many sub-problems an optimal solution to the problem uses?
ii. How many choices we have in determining which sub-problems to use in an optimal
solution?
152. Dynamic Programming
2. Overlapping Sub-problems: The space of sub-problems must be “small”
in the sense that a recursive algorithm for the problem solves the same sub-
problems over and over, rather than always generating new sub-problems
• Total number of distinct sub-problems is polynomial in 𝑛
• Divide-and-Conquer approach generates brand-new (non-overlapping) problems at
each step of the recursion
• Dynamic Programming approach takes advantage of overlapping sub-problems by
solving each sub-problem once and then storing its solution in a table where it can be
looked up when needed, using constant time per lookup
153. Dynamic Programming
S# Characteristic Divide-and-Conquer Dynamic Programming
1 Problems Non-Optimization Optimization
2 Sub-problems (Divide) Disjoint Overlapping
3 Solves sub-problems (Conquer)
Recursively and
Repeatedly
Recursively but
Only once
4 Saves solutions to sub-problems No Table
5
Combines solutions to sub-problems
(Combine)
Yes Yes
6 Time Efficient Less More
7 Space Efficient More Less
154. Dynamic Programming
• When developing a Dynamic Programming algorithm, we follow a sequence of four steps:
1. Characterize the structure of an optimal solution
• Find the Optimal Substructure
2. Recursively define the value of an optimal solution
• Define the cost of an optimal solution recursively in terms of the optimal solutions to sub-problems
3. Compute the value of an optimal solution, typically in a bottom-up fashion
• Write an algorithm to compute the value of an optimal solution
4. Construct an optimal solution from computed information
• An optional step
155. Dynamic Programming
• Total Running Time:
• Depends on the product of two factors:
1. Total number of sub-problems
2. Number of choices for each sub-problem
156. Rod Cutting
• Cutting a Steel Rod into rods of smaller length in a way that maximizes
their total value
• Serling Enterprises buys long steel rods and cuts them into shorter rods,
which it then sells. Each cut is free. The management of Serling Enterprises
wants to know the best way to cut up the rods.
• We assume that we know, for 𝑖 = 1, 2, … the price 𝑝𝑖 in dollars that Serling
Enterprises charges for a rod of length 𝑖 inches. Rod lengths are always an
integral number of inches.
158. Rod Cutting: Design
• Method 𝟏: Possible Combinations
• Consider the case when 𝒏 = 𝟒
• Figure shows all the unique ways (8) to cut up a rod of 4 inches in length, including
the way with no cuts at all
• Cutting a 4-inch rod into two 2-inch pieces produces revenue 𝑝2 + 𝑝2 = 5 + 5 = 10,
which is optimal
• Total Possible Combinations of cutting up a rod of length 𝑛 = 𝟐𝒏−𝟏
• 𝑇 𝑛 = 𝛩(𝟐𝒏−𝟏) = 𝛩(𝟐𝒏)
164. Rod Cutting: Design
• 𝑝𝑛 corresponds to making no cuts at all and selling the rod of length 𝑛 as is
• Other 𝑛 − 1 arguments correspond to the revenue obtained by making an initial
cut of the rod into two pieces of size 𝑖 and 𝑛 − 𝑖, for each 𝑖 = 1,2, … , 𝑛 − 1,
and then optimally cutting up those pieces further, obtaining revenues 𝑟𝑖 and 𝑟𝑛−𝑖
from those two pieces
• Since we don’t know ahead of time which value of 𝑖 optimizes revenue, we must
consider all possible values of 𝑖 and pick the one that maximizes revenue
• We also have the option of picking no 𝑖 at all if we can obtain more revenue by
selling the rod uncut
165. Rod Cutting: Design
• Consider the case when 𝒏 = 𝟓
𝑟5 = max 𝑝5, 𝑟1 + 𝑟4, 𝑟2 + 𝑟3, 𝑟3 + 𝑟2, 𝑟4 + 𝑟1
𝑟1 = max 𝑝1 = max 1 = 1
𝑟2 = max 𝑝2, 𝑟1 + 𝑟1 = max 5, 1 + 1 = max 5, 2 = 5
𝑟3 = max 𝑝3, 𝑟1 + 𝑟2, 𝑟2 + 𝑟1 = max 8, 1 + 5, 5 + 1 = max 8, 6, 6 = 8
𝑟4 = max 𝑝4, 𝑟1 + 𝑟3, 𝑟2 + 𝑟2, 𝑟3 + 𝑟1 = max 9, 1 + 8, 5 + 5, 8 + 1 = max 9, 9, 10, 9 = 10
𝑟5 = max 10, 1 + 10, 5 + 8, 8 + 5, 10 + 1 = max 10, 11, 13, 13, 11 = 13
• Tracing back the optimal solution 5 = 2 + 3
166. Rod Cutting: Design
• To solve the original problem of size 𝒏, we solve problems of the same
type, but of smaller sizes
• Once we make the first cut, we may consider the two pieces as independent
instances of the rod-cutting problem
• Which Method is better?
169. Rod Cutting: Analysis
• Optimal solution for cutting up a rod of length 𝑛 (if we make any cuts at all)
uses just one sub-problem (of size 𝑛 − 𝑖), but we must consider 𝒏 − 𝟏
choices for 𝑖 in order to determine which one yields an optimal solution
• Optimal way of cutting up a rod of length 𝑛 (if we make any cuts at all)
involves optimally cutting up the two pieces resulting from the first cut
• Overall optimal solution incorporates optimal solutions to the two related
sub-problems, maximizing revenue from each of those two pieces
• Rod-cutting problem exhibits Optimal Substructure
170. Comparative Analysis of Methods
S# Method/ Case General Best Average Worst
1 Possible Combinations 𝜣(𝟐𝒏
) - - -
2 Equation 1 𝜣(𝟐𝒏
) - - -
171. Rod Cutting: Design
• Method 𝟑: Equation 2
• Top-down
• Recursive
• A decomposition is viewed as consisting of a first piece of length 𝑖 cut off the left end,
and then a remainder of length 𝑛 − 𝑖
• Only the remainder, and not the first piece, may be further divided
• An optimal solution represents the solution based on only one related sub-problem;
the remainder, instead of two sub-problems
• Simpler than Methods 1 and 2
172. Rod Cutting: Design
• Consider the case when 𝒏 = 𝟓
𝑟5 = max
1≤𝑖≤5
𝑝𝑖 + 𝑟5−𝑖 = max(𝑝1 + 𝑟4, 𝑝2 + 𝑟3, 𝑝3 + 𝑟2, 𝑝4 + 𝑟1, 𝑝5 + 𝑟0)
𝑟0 = 0
𝑟1 = max 𝑝1 + 𝑟0 = max 1 + 0 = max 1 = 1
𝑟2 = max 𝑝1 + 𝑟1, 𝑝2 + 𝑟0 = max 1 + 1, 5 + 0 = max 2, 5 = 5
𝑟3 = max 𝑝1 + 𝑟2, 𝑝2 + 𝑟1, 𝑝3 + 𝑟0 = max 1 + 5, 5 + 1, 8 + 0 = max 6, 6, 8 = 8
𝑟4
= max(𝑝1 + 𝑟3, 𝑝2 + 𝑟2, 𝑝3 + 𝑟1, 𝑝4
173. Rod Cutting: Design
𝑟5 = max 𝑝1 + 𝑟4, 𝑝2 + 𝑟3, 𝑝3 + 𝑟2, 𝑝4 + 𝑟1, 𝑝5 + 𝑟0
= max 1 + 10, 5 + 8, 8 + 5, 9 + 1 , 10 + 0 = max 11, 13, 13, 10, 10 = 13
• Tracing back the optimal solution 5 = 2 + 3
• Which Method is the best?
188. Rod Cutting: Analysis
• Each node label gives the size 𝒏 of the corresponding immediate sub-problems
• An edge from parent 𝒔 to child 𝒕 corresponds to cutting off an initial piece of size
𝒔 − 𝒕, and leaving a remaining sub-problem of size 𝒕
• Total nodes = 𝟐𝒏
• Total leaves = 𝟐𝒏−𝟏
• Total number of paths from root to a leaf = 𝟐𝒏−𝟏
• Total ways of cutting up a rod of length 𝑛 = 𝟐𝒏−𝟏 = Possible Combinations
(Method 1)
189. Rod Cutting: Analysis
• Each node label represents the number of immediate calls made to CUT-ROD by that node
• 𝑪𝑼𝑻 − 𝑹𝑶𝑫 𝒑, 𝒏 calls 𝑪𝑼𝑻 − 𝑹𝑶𝑫 𝒑, 𝒏 − 𝒊 for 𝑖 = 1, 2, … . , 𝑛 (top to down, left to right in graph)
• 𝑪𝑼𝑻 − 𝑹𝑶𝑫 𝒑, 𝒏 calls 𝑪𝑼𝑻 − 𝑹𝑶𝑫 𝒑, 𝒋 for 𝑗 = 0, 1, … . , 𝑛 − 1 (top to down, right to left in graph)
• Let 𝑻(𝒏) denote the total number of calls made to CUT-ROD, when called with its second parameter equal
to 𝑛
• 𝑻(𝒏) equals the sum of number of nodes in sub-trees whose root is labeled 𝑛 in the recursion tree
• One call to CUT-ROD is made at the root:
𝑇(0) = 1
190. Rod Cutting: Analysis
• 𝑻(𝒋) denotes the total number of calls (including recursive calls) made due to 𝑪𝑼𝑻 − 𝑹𝑶𝑫(𝒑, 𝒏 − 𝒊), where 𝑗 = 𝑛 − 𝑖
𝑇 𝑛 = 1 +
𝑗=0
𝑛−1
𝑇(𝑗)
𝑇 𝑛 = 1 +
𝑗=0
𝑛−1
2𝑗
= 1 + 20
+ 21
+ ⋯ + 2𝑛−1
= 1 + 2𝑛
− 1
= 𝛩(2𝑛
)
• Running Time of CUT-ROD is exponential
• For each unit increment in 𝑛, program’s running time doubles
191. Comparative Analysis of Methods
S# Method/ Case General Best Average Worst
1 Possible Combinations 𝜣(𝟐𝒏
) - - -
2 Equation 1 𝜣(𝟐𝒏
) - - -
3 Equation 2 𝜣(𝟐𝒏
) - - -
4 Automation of Method 𝟑 𝜣(𝟐𝒏
) - - -
192. Rod Cutting: Design
• Dynamic Programming
• Each sub-problem is solved only once, and the solution is saved
• Look up the solution in constant time, rather than re-compute it
• Time-Space trade-off
• Additional memory is used to save computation time
• Exponential-time solution may be transformed into Polynomial-time solution
i. Total number of distinct sub-problems is polynomial in 𝑛
ii. Each sub-problem can be solved in polynomial time
1. Top-down with Memoization
2. Bottom-up Method
193. Rod Cutting: Design
• Method 𝟓: Top-down with Memoization
• Top-down
• Recursive
• Saves solutions of all sub-problems
• Solves each sub-problem only once
• Memoized:
• Solutions initially contain special values to indicate that the solutions need to be computed
• Remembers solutions computed earlier
• Checks whether solutions of sub-problems have been saved earlier
• Memoized version of Method 4
209. Rod Cutting: Analysis
𝑇 𝑛 = Total number of sub-problems * Number of choices for each sub-problem
= 𝑛 ∗ 𝑛
= 𝜣(𝒏𝟐)
210. Comparative Analysis of Methods
S# Method/ Case General Best Average Worst
1 Possible Combinations 𝜣(𝟐𝒏
) - - -
2 Equation 1 𝜣(𝟐𝒏
) - - -
3 Equation 2 𝜣(𝟐𝒏
) - - -
4 Automation of Method 𝟑 𝜣(𝟐𝒏
) - - -
5 Top-down with Memoization 𝜣(𝒏𝟐) - - -
211. Rod Cutting: Design
• Method 𝟔: Bottom-up Method
• Bottom-up
• Non-Recursive or Iterative
• Sorts all sub-problems by size and solves them in that order (smallest first)
• When solving a particular sub-problem, all of the smaller sub-problems its solution
depends upon, have already been solved
• Saves solutions of all sub-problems
• Solves each sub-problem only once
220. Rod Cutting: Analysis
𝑇 𝑛 = Total number of sub-problems * Number of choices for each sub-problem
= 𝑛 ∗ 𝑛
= 𝜣(𝒏𝟐)
221. Comparative Analysis of Methods
S# Method/ Case General Best Average Worst
1 Possible Combinations 𝜣(𝟐𝒏
) - - -
2 Equation 1 𝜣(𝟐𝒏
) - - -
3 Equation 2 𝜣(𝟐𝒏
) - - -
4 Automation of Method 𝟑 𝜣(𝟐𝒏
) - - -
5 Top-down with Memoization 𝜣(𝒏𝟐) - - -
6 Bottom-Up Method 𝜣(𝒏𝟐) - - -
222. Rod Cutting: Analysis
S# Characteristic Top-down with Memoization Bottom-up Method
1 Strategy Top-down Bottom-up
2 Type Recursive Iterative
3 Memoized Yes No
4 Sorts all sub-problems No Yes
5 Solves sub-problems Top-down, Left-to-Right Top-down, Right-to-Left
6 Running Time Θ(𝑛2) Θ(𝑛2)
223. Rod Cutting: Analysis
• Sub-problem Graphs
• 𝐺 = (𝑉, 𝐸)
• Reduced or Collapsed version of Recursion Tree
• All nodes with the same label are collapsed into a single vertex
• All edges go from parent to child
• Each vertex label represents the size of the corresponding sub-problem, and each directed edge (𝒙, 𝒚) indicates the
need for an optimal solution to sub-problem 𝒚, when determining an optimal solution to sub-problem 𝒙
• Each vertex corresponds to a distinct sub-problem, and the choices for a sub-problem are the edges incident to
that sub-problem
• 𝑇 𝑛 = 𝑉 ∗ 𝐸 = 𝑛 ∗ 𝑛 = 𝜣(𝒏𝟐
)
225. Rod Cutting: Design
• Method 7: Bottom-Up Method with Optimal Solution
• Determines Optimal Solution (along with Optimal Value)
• Determines Optimal Size of the first piece to cut off
• Extension of Method 6
238. Rod Cutting: Analysis
𝑇 𝑛 = Total number of sub-problems * Number of choices for each sub-problem
= 𝑛 ∗ 𝑛
= 𝜣(𝒏𝟐)
239. Comparative Analysis of Methods
S# Method/ Case General Best Average Worst
1 Possible Combinations 𝜣(𝟐𝒏
) - - -
2 Equation 1 𝜣(𝟐𝒏
) - - -
3 Equation 2 𝜣(𝟐𝒏
) - - -
4 Automation of Method 𝟑 𝜣(𝟐𝒏
) - - -
5 Top-down with Memoization 𝜣(𝒏𝟐) - - -
6 Bottom-Up Method 𝜣(𝒏𝟐) - - -
7
Bottom-Up Method with Optimal
Solution
𝜣(𝒏𝟐) 𝜣(𝒏𝟐) 𝜣(𝒏𝟐) 𝜣(𝒏𝟐)
240. Rod Cutting: Design
• Method 8: Bottom-Up Method with Optimal Decomposition
• Determines First Optimal Decomposition (alongwith Optimal Value)
• Determines Optimal Sizes of all the pieces to cut off
• Extension of Method 7
248. Rod Cutting: Analysis
𝑇 𝑛 = Total number of sub-problems * Number of choices for each sub-problem
= 𝑛 ∗ 𝑛
= 𝜣(𝒏𝟐)
249. Comparative Analysis of Methods
S# Method/ Case General Best Average Worst
1 Possible Combinations 𝜣(𝟐𝒏
) - - -
2 Equation 1 𝜣(𝟐𝒏
) - - -
3 Equation 2 𝜣(𝟐𝒏
) - - -
4 Automation of Method 𝟑 𝜣(𝟐𝒏
) - - -
5 Top-down with Memoization 𝜣(𝒏𝟐) - - -
6 Bottom-Up Method 𝜣(𝒏𝟐) - - -
7
Bottom-Up Method with Optimal
Solution
𝜣(𝒏𝟐) 𝜣(𝒏𝟐) 𝜣(𝒏𝟐) 𝜣(𝒏𝟐)
8
Bottom-Up Method with Optimal
Decomposition
𝜣(𝒏𝟐) 𝜣(𝒏𝟐) 𝜣(𝒏𝟐) 𝜣(𝒏𝟐)
250. Greedy Algorithm
• A greedy algorithm is a simple, intuitive algorithm that is used in
optimization problems.
• The algorithm makes the optimal choice at each step as it attempts to find
the overall optimal way to solve the entire problem.
• Greedy algorithms are quite successful in some problems, such as Huffman
encoding which is used to compress data, or Dijkstra's algorithm, which is
used to find the shortest path through a graph.
251. Activity-Selection Problem
• The Activity Selection Problem is an optimization problem which deals with the
selection of non-conflicting activities that needs to be executed by a single person
or machine in each time frame.
• Each activity is marked by a start and finish time. Greedy technique is used for
finding the solution since this is an optimization problem.
• Let's consider that you have n activities with their start and finish times, the
objective is to find solution set having maximum number of non-conflicting
activities that can be executed in a single time frame, assuming that only one person
or machine is available for execution.
252. Activity-Selection Problem
• Some points to note here:
• It might not be possible to complete all the activities, since their timings can collapse.
• Two activities, say i and j, are said to be non-conflicting if si >= fj or sj >= fi where si
and sj denote the starting time of activities i and j respectively, and fi and fj refer to the
finishing time of the activities i and j respectively.
• Greedy approach can be used to find the solution since we want to maximize
the count of activities that can be executed. This approach will greedily
choose an activity with earliest finish time at every step, thus yielding an
optimal solution.
253. Steps for Activity Selection Problem
• Following are the steps we will be following to solve the activity selection problem,
• Step 1: Sort the given activities in ascending order according to their finishing time.
• Step 2: Select the first activity from sorted array act[] and add it to sol[] array.
• Step 3: Repeat steps 4 and 5 for the remaining activities in act[].
• Step 4: If the start time of the currently selected activity is greater than or equal to the finish
time of previously selected activity, then add it to the sol[] array.
• Step 5: Select the next activity in act[] array.
• Step 6: Print the sol[] array.
254. Algorithm
• GREEDY- ACTIVITY SELECTOR (s, f)
• n ← length [s]
• A ← {1}
• j ← 1.
• for i ← 2 to n
• do if si ≥ fi
• then A ← A ∪ {i}
• j ← i
• return A
255. Example
• S = (A1 A2 A3 A4 A5 A6 A7 A8 A9 A10)
• Si = (1,2,3,4,7,8,9,9,11,12)
• fi = (3,5,4,7,10,9,11,13,12,14)
256. Example
• Now, schedule A1
• Next schedule A3 as A1 and A3 are non-interfering.
• Next skip A2 as it is interfering.
• Next, schedule A4 as A1 A3 and A4 are non-interfering, then next, schedule A6 as A1 A3 A4 and A6
are non-interfering.
• Skip A5 as it is interfering.
• Next, schedule A7 as A1 A3 A4 A6 and A7 are non-interfering.
• Next, schedule A9 as A1 A3 A4 A6 A7 and A9 are non-interfering.
257. Example
• Skip A8 as it is interfering.
• Next, schedule A10 as A1 A3 A4 A6 A7 A9 and A10 are non-interfering.
• Thus, the final Activity schedule is:
• Activity Selection Problem
258.
259. Time Complexity Analysis
• Following are the scenarios for computing the time complexity of Activity
Selection Algorithm:
• Case 1: When a given set of activities are already sorted according to their finishing
time, then there is no sorting mechanism involved, in such a case the complexity of the
algorithm will be O(n)
• Case 2: When a given set of activities is unsorted, then we will have to use the sort()
method defined in bits/stdc++ header file for sorting the activities list. The time
complexity of this method will be O(nlogn), which also defines complexity of the
algorithm.
260. Real-life Applications of Activity Selection
Problem
• Following are some of the real-life applications of this problem:
• Scheduling multiple competing events in a room, such that each event has its own start
and end time.
• Scheduling manufacturing of multiple products on the same machine, such that each
product has its own production timelines.
• Activity Selection is one of the most well-known generic problems used in Operations
Research for dealing with real-life business problems.
261. Huffman Codes
• Every information in computer science is encoded as strings of 1s and 0s.
• The objective of information theory is to usually transmit information using fewest
number of bits in such a way that every encoding is unambiguous.
• This tutorial discusses about fixed-length and variable-length encoding along with
Huffman Encoding which is the basis for all data encoding schemes
• Encoding, in computers, can be defined as the process of transmitting or storing
sequence of characters efficiently.
• Fixed-length and variable length are two types of encoding schemes
Syed Zaid Irshad
262. Encoding Schemes
• Fixed-Length encoding - Every character is assigned a binary code using
same number of bits. Thus, a string like “aabacdad” can require 64 bits (8
bytes) for storage or transmission, if each character uses 8 bits.
• Variable- Length encoding - As opposed to Fixed-length encoding, this
scheme uses variable number of bits for encoding the characters depending
on their frequency in the given text. Thus, for a given string like “aabacdad”,
frequency of characters ‘a’, ‘b’, ‘c’ and ‘d’ is 4,1,1 and 2 respectively. Since ‘a’
occurs more frequently than ‘b’, ‘c’ and ‘d’, it uses least number of bits,
followed by ‘d’, ‘b’ and ‘c’.
263. Example
• Suppose we randomly assign binary codes to each character as follows- a 0 b 011 c 111 d 11
• Thus, the string “aabacdad” gets encoded to 00011011111011 (0 | 0 | 011 | 0 | 111 | 11 | 0 | 11),
using fewer number of bits compared to fixed-length encoding scheme.
• But the real problem lies with the decoding phase. If we try and decode the string 00011011111011, it
will be quite ambiguous since, it can be decoded to the multiple strings, few of which are-
• aaadacdad (0 | 0 | 0 | 11 | 0 | 111 | 11 | 0 | 11) aaadbcad (0 | 0 | 0 | 11 | 011 | 111 | 0 | 11)
aabbcb (0 | 0 | 011 | 011 | 111 | 011)
264. Example
• To prevent such ambiguities during decoding, the encoding phase should satisfy the “prefix rule”
which states that no binary code should be a prefix of another code. This will produce uniquely
decodable codes. The above codes for ‘a’, ‘b’, ‘c’ and ‘d’ do not follow prefix rule since the binary code
for a, i.e., 0, is a prefix of binary code for b i.e 011, resulting in ambiguous decodable codes.
• Let's reconsider assigning the binary codes to characters ‘a’, ‘b’, ‘c’ and ‘d’.
• a 0 b 11 c 101 d 100
• Using the above codes, string “aabacdad” gets encoded to 001101011000100 (0 | 0 | 11 | 0 | 101 |
100 | 0 | 100). Now, we can decode it back to string “aabacdad”.
265. Huffman Encoding
• Huffman Encoding can be used for finding solution to the given problem statement.
• Developed by David Huffman in 1951, this technique is the basis for all data compression
and encoding schemes
• It is a famous algorithm used for lossless data encoding
• It follows a Greedy approach, since it deals with generating minimum length prefix-free
binary codes
• It uses variable-length encoding scheme for assigning binary codes to characters depending
on how frequently they occur in the given text. The character that occurs most frequently is
assigned the smallest code and the one that occurs least frequently gets the largest code
266. Algorithm Steps
• Step 1- Create a leaf node for each character and build a min heap using all the
nodes (The frequency value is used to compare two nodes in min heap)
• Step 2- Repeat Steps 3 to 5 while heap has more than one node
• Step 3- Extract two nodes, say x and y, with minimum frequency from the heap
• Step 4- Create a new internal node z with x as its left child and y as its right child.
Also, frequency(z)= frequency(x)+frequency(y)
• Step 5- Add z to min heap
• Step 6- Last node in the heap is the root of Huffman tree
267. Algorithm
• Huffman (C)
• n=|C|
• Q ← C
• for i=1 to n-1
• do
• z= allocate-Node ()
• x= left[z]=Extract-Min(Q)
• y= right[z] =Extract-Min(Q)
• f [z]=f[x]+f[y]
• Insert (Q, z)
• return Extract-Min (Q)
273. Time Complexity
• Since Huffman coding uses min Heap data structure for implementing
priority queue, the complexity is O(nlogn). This can be explained as follows-
• Building a min heap takes O(nlogn) time (Moving an element from root to leaf node
requires O(logn) comparisons and this is done for n/2 elements, in the worst case).
• Building a min heap takes O(nlogn) time (Moving an element from root to leaf node
requires O(logn) comparisons and this is done for n/2 elements, in the worst case).
• Since building a min heap and sorting it are executed in sequence, the
algorithmic complexity of entire process computes to O(nlogn)
274. Graph
• A Graph is a non-linear data structure
consisting of nodes and edges.
• The nodes are sometimes also referred to as
vertices and the edges are lines or arcs that
connect any two nodes in the graph.
• More formally a Graph can be defined as, A
Graph consists of a finite set of vertices(or
nodes) and set of Edges which connect a
pair of nodes.
275. Breadth First Search
• Breadth-First Traversal (or Search) for a graph is like Breadth-First Traversal
of a tree.
• The only catch here is, unlike trees, graphs may contain cycles, so we may
come to the same node again.
• To avoid processing a node more than once, we use a Boolean visited array.
• For simplicity, it is assumed that all vertices are reachable from the starting
vertex.
287. Time Complexity
• Following is Breadth First Traversal (starting from vertex 2):
• 2 0 3 1
• Time Complexity: O(V+E) where V is several vertices in the graph and E is
several edges in the graph.
288. Depth First Search
• Depth First Traversal (or Search) for a graph is like Depth First Traversal of
a tree.
• The only catch here is, unlike trees, graphs may contain cycles (a node may
be visited twice).
• To avoid processing a node more than once, use a Boolean visited array.
290. Complexity Analysis
• Time complexity: O(V + E), where V is the number of vertices and E is the
number of edges in the graph.
• Space Complexity: O(V), since an extra visited array of size V is required.
291. Topological Sorting
• Topological sorting for Directed Acyclic Graph (DAG) is a linear ordering of
vertices such that for every directed edge u v, vertex u comes before v in the
ordering.
• Topological Sorting for a graph is not possible if the graph is not a DAG.
• In DFS, we print a vertex and then recursively call DFS for its adjacent vertices.
• In topological sorting, we need to print a vertex before its adjacent vertices.
• So Topological sorting is different from DFS.
292. DFS vs TS
• In DFS, we start from a vertex, we first print it and then recursively call DFS
for its adjacent vertices.
• In topological sorting, we use a temporary stack.
• We don’t print the vertex immediately, we first recursively call topological
sorting for all its adjacent vertices, then push it to a stack. Finally, print
contents of the stack.
• Note that a vertex is pushed to stack only when all its adjacent vertices (and
their adjacent vertices and so on) are already in the stack.
297. Complexity Analysis
• Time Complexity: O(V+E).
• The above algorithm is simply DFS with an extra stack. So, time complexity is the same
as DFS which is.
• Auxiliary space: O(V).
• The extra space is needed for the stack.
298. Strongly
Connected
Components
• A directed graph is
strongly connected if
there is a path between all
pairs of vertices.
• A strongly connected
component (SCC) of a
directed graph is a
maximal strongly
connected subgraph.
299. Kosaraju’s Algorithm
For each vertex u of the graph, mark u as unvisited. Let L be empty.
For each vertex u of the graph do Visit(u), where Visit(u) is the recursive subroutine:
If u is unvisited then:
Mark u as visited.
For each out-neighbour v of u, do Visit(v).
Prepend u to L.
Otherwise do nothing.
300. Kosaraju’s Algorithm
For each element u of L in order, do Assign(u,u) where Assign(u,root) is the recursive
subroutine:
If u has not been assigned to a component, then:
Assign u as belonging to the component whose root is root.
For each in-neighbour v of u, do Assign(v,root).
Otherwise do nothing.
301. Steps
• Create an empty stack ‘S’ and do DFS traversal of a graph. In DFS traversal,
after calling recursive DFS for adjacent vertices of a vertex, push the vertex
to stack.
• Reverse directions of all arcs to obtain the transpose graph.
• One by one pop a vertex from S while S is not empty. Let the popped vertex
be ‘v’. Take v as source and do DFS. The DFS starting from v prints strongly
connected component of v.