# Top 20 important questions of Design and analysis of algorithm – { Part B }.

Design and analysis of algorithms are fundamental topics in computer science and play a crucial role in the development of efficient and effective solutions to various problems. Whether it’s a simple sorting algorithm or a complex graph traversal, understanding the design and analysis of algorithms is essential for any computer scientist. In this blog post, we will be discussing the top 20 most important questions related to the design and analysis of algorithms.

These questions will cover a wide range of topics, from basic algorithms and data structures to advanced concepts like computational complexity and optimization.

**1. Describe in details about algorithm using Insertion Sort.**

Insertion sort is a simple sorting algorithm that builds the final sorted list one item at a time. It is an in-place sorting algorithm, meaning it sorts the list in place and does not require any additional memory to hold the sorted list. The basic idea behind the algorithm is to iterate through the list, comparing each element to the elements before it, and inserting it in its correct position.

Here is the basic pseudocode for the insertion sort algorithm:

- Set a variable i to 1. This variable will be used to iterate through the list.
- Set a variable j to i. This variable will be used to compare the current element to the elements before it.
- Iterate through the list starting at i and ending at the last element.
- Compare the current element at index i to the element at index j.
- If the element at index i is less than the element at index j, swap the two elements.
- Decrement j by 1 and repeat step 4 and 5 until the element at index i is greater than or equal to the element at index j.
- Increment i by 1 and repeat steps 3-6 until the entire list is sorted.

Insertion sort is efficient for small lists and lists that are already partially sorted. The time complexity of the algorithm is O(n^2) in the worst case and O(n) in the best case. The best case occurs when the list is already sorted and the worst case occurs when the list is sorted in reverse order.

It is also worth noting that insertion sort is a stable sorting algorithm, meaning that it preserves the relative order of elements with equal keys.

**2.Explain the general framework for analyzing the efficiency of algorithm**

Analyzing the efficiency of an algorithm is the process of determining the amount of resources (such as time or space) required by the algorithm to solve a problem of a certain size. There are several ways to analyze the efficiency of an algorithm, but some common methods include:

**Time complexity**: This refers to the amount of time required by the algorithm to solve a problem of a certain size. Time complexity is usually measured in terms of the number of basic operations (such as comparisons or assignments) performed by the algorithm. The most common way to express time complexity is using big O notation, which describes the upper bound of the number of operations required by the algorithm.**Space complexity**: This refers to the amount of memory required by the algorithm to solve a problem of a certain size. Space complexity is usually measured in terms of the amount of memory required to store the input, output, and any additional data structures used by the algorithm. Like time complexity, the space complexity is often expressed using big O notation.**Best, worst, and average case analysis**: These are ways of measuring the performance of an algorithm for different inputs. The best-case scenario is the input for which the algorithm performs the best, the worst-case scenario is the input for which the algorithm performs the worst, and the average case scenario is the average performance of the algorithm for all inputs.**Empirical analysis:**This is a way of measuring the performance of an algorithm by running experiments with different inputs and measuring the actual running time of the algorithm. Empirical analysis is useful for comparing the performance of different algorithms or for comparing the performance of an algorithm to its theoretical performance.

In general, the framework for analyzing the efficiency of an algorithm involves identifying the basic operations performed by the algorithm, determining how the number of these operations scales with the size of the input, and expressing the result in big O notation.

**3. Differentiate Time and Space complexity estimation.**

Time complexity and space complexity are both methods used to analyze the efficiency of an algorithm, but they refer to different types of resources used by the algorithm.

**Time complexity** refers to the amount of time required by the algorithm to solve a problem of a certain size. It is usually measured in terms of the number of basic operations (such as comparisons or assignments) performed by the algorithm. The time complexity is important because it determines how long the algorithm will take to execute for a given input size, which is a key factor in determining the overall performance of the algorithm.

**Space complexity**, on the other hand, refers to the amount of memory required by the algorithm to solve a problem of a certain size. It is usually measured in terms of the amount of memory required to store the input, output, and any additional data structures used by the algorithm. Space complexity is important because it determines how much memory the algorithm will use, which can be a key factor in determining the overall performance of the algorithm, especially for large inputs or in memory-constrained environments.

In summary, Time complexity analyses how many operations an algorithm takes to complete its task and Space complexity analyses how much memory an algorithm takes to complete its task. Both time and space complexity are often expressed using big O notation, which describes the upper bound of the number of operations or amount of memory required by the algorithm.

**4. Discuss in detail about fundamentals of algorithmic problem solving?**

Algorithmic problem solving is the process of using a systematic and logical approach to solving a problem or achieving a specific task. It involves breaking down a problem into smaller, manageable sub-problems, and then designing and implementing a solution that can be expressed in a step-by-step process.

The following are some of the fundamentals of algorithmic problem solving:

**Understand the problem**: Before attempting to solve a problem, it is important to fully understand the problem statement and the requirements of the task. This includes understanding the input, output, and any constraints or limitations of the problem.**Break down the problem**: Once the problem is understood, it is important to break it down into smaller, manageable sub-problems. This makes it easier to design and implement a solution, and also allows for the identification of any common patterns or structures in the problem.**Design a solution**: After breaking down the problem, the next step is to design a solution. This involves using a systematic and logical approach to solving the problem, such as using a specific algorithm or data structure. The solution should be expressed in a step-by-step process and be easy to understand and implement.**Implement and test the solution**: Once a solution is designed, it must be implemented and tested using sample inputs and outputs. This step is important to ensure that the solution is correct and works as expected.**Analyze the solution**: After testing the solution, it’s important to analyze the solution’s performance and efficiency. This includes determining the time and space complexity of the solution and comparing it to other possible solutions.**Optimize the solution**: Based on the analysis, if the solution is not efficient enough, it can be optimized. This can involve simplifying the solution, reducing the number of operations, or using a more efficient algorithm or data structure.

In summary, algorithmic problem solving is a systematic and logical approach to solving a problem or achieving a specific task. It involves understanding the problem, breaking it down into smaller sub-problems, designing a solution, implementing and testing the solution, analyzing the solution and optimizing it if necessary.

**5. Solve the Recurrence : T(n) = 2T(n/2) + nlgn**

The recurrence T(n) = 2T(n/2) + nlogn is a common form of recurrence that can be solved using the Master Theorem. The Master Theorem is a technique used to solve recurrences of the form T(n) = aT(n/b) + f(n) where a and b are constants and f(n) is a function of n.

In this case, T(n) = 2T(n/2) + nlogn, we can see that:

a = 2, b = 2 and f(n) = nlogn

This recurrence has the form of the Master Theorem case 2, where a > b^c and f(n) = n^c * logn^(k), for some constant c and k.

So by applying the case 2 of Master Theorem, we get T(n) = O(nlogn)

The Master Theorem states that if a > b^c, then T(n) is O(n^c * log^k n) which is true in this case, T(n) = O(nlogn)

Hence the solution of this recurrence T(n) = 2T(n/2) + nlogn is O(nlogn)

**Note:** Master Theorem is a powerful technique that can quickly give the solution of a recurrence, but it has some limitations and its validity depends on the form of the recurrence equation, it’s important to check if a recurrence is in the form of Master Theorem before using it.

**6. Determine the order of the growth for any two functions using limits.**

The order of growth of a function is a way of describing how the function behaves as the input size becomes large. The order of growth of a function is often described using big O notation, which describes the upper bound of the function’s growth.

To determine the order of growth for two functions using limits, we can take the limit of the ratio of the two functions as the input size approaches infinity. If the limit exists and is a finite non-zero constant, then the two functions have the same order of growth. If the limit is infinity, then the first function grows faster than the second function. If the limit is zero, then the second function grows faster than the first function.

**For example**, consider the functions f(n) = n^2 and g(n) = 2^n. To determine the order of growth of these two functions, we can take the limit of f(n) / g(n) as n approaches infinity:

lim (n^2 / 2^n) = lim (n^2 / 2^n) = 0

The limit is zero, which means that the function g(n) = 2^n grows faster than the function f(n) = n^2. We can express this by saying that g(n) is O(2^n) and f(n) is O(n^2)

Similarly, to compare the functions f(n) = n^2 and h(n) = n^3, we can take the limit of f(n) / h(n) as n approaches infinity:

lim (n^2 / n^3) = lim (1/n) = 0

This means that h(n) = n^3 grows faster than f(n) = n^2, we can express this by saying that h(n) is O(n^3) and f(n) is O(n^2)

It is also important to note that this method only gives us an upper bound of the growth of the function, so it’s not able to prove that two functions have the same order of growth, but it can only prove that one function is faster than the other one.

**7. Solve the following recurrence relations T(n) = 2T(n/2)+2 n>2 1 n=2 0 n=1**

The recurrence relation T(n) = 2T(n/2) + 2 for n > 2, T(n) = 1 for n = 2 and T(n) = 0 for n = 1 is a form of divide-and-conquer recurrence relation, where the problem is divided into two sub-problems of size n/2 and then the solutions to the sub-problems are combined.

To solve this recurrence, we can use the method of substitution. We start by assuming that T(n) = c*n^k for some constants c and k. Then we substitute this into the recurrence and see if it holds true for all values of n.

T(n) = 2T(n/2) + 2 = 2(c(n/2)^k) + 2 = cn^k + 2

Since the recurrence holds true for n = 2 and n = 1, and T(n) = cn^k + 2 is a valid solution for n > 2, then the solution for the recurrence is T(n) = cn^k + 2 for n > 2, T(n) = 1 for n = 2 and T(n) = 0 for n = 1.

By comparing the form of T(n) = cn^k + 2 with the big O notation, we can see that the order of the function is O(n^k) and the constant c is 2.

Therefore, the final solution for the recurrence T(n) = 2T(n/2) + 2 for n > 2, T(n) = 1 for n = 2 and T(n) = 0 for n = 1 is O(n^k) with k = 1.

**8. Solve the Recurrence : T(n) = 5T(n-1) – 6T(n-2) ; T(1) = 1**

The recurrence T(n) = 5T(n-1) – 6T(n-2) with T(1) = 1 is a type of recurrence called a linear homogeneous recurrence with constant coefficients. This type of recurrence can be solved using characteristic equations.

The characteristic equation is a polynomial equation whose roots are the solutions to the recurrence. To find the characteristic equation, we substitute the general solution T(n) = r^n into the recurrence and get:

r^n = 5r^(n-1) – 6r^(n-2)

Expanding the right side and moving the r^(n-2) term to the left side gives:

r^n = 5r^(n-1) – 6r^(n-2) r^n – 5r^(n-1) + 6r^(n-2) = 0

To find the roots of the characteristic equation, we set it equal to zero and solve for r:

r^2 – 5r + 6 = 0 (r-3)(r-2) = 0

The roots of the characteristic equation are r1 = 3 and r2 = 2

The general solution to the recurrence is T(n) = c1*3^n + c2*2^n, where c1 and c2 are constants that can be determined from the initial conditions.

Given T(1) = 1, we can substitute n = 1 into the general solution and solve for c1 and c2:

T(1) = c1*3^1 + c2*2^1 1 = 3c1 + 2c2 c1 = (1-2c2)/3

Now, we can substitute the value of c1 back into general solution to find the closed form solution for T(n)

T(n) = (1-2c2)*3^n + c2*2^n

We have to find the value of c2, by substituting the given initial condition, we have

1 = (1-2c2)*3 + 2c2 1 = 3 – 2c2 + 2c2 1 = 3 c2 = 0

Therefore, the closed form solution for T(n) = (1-2c2)*3^n + c2*2^n becomes T(n) = 3^n.

Hence the solution of the recurrence T(n) = 5T(n-1) – 6T(n-2) is O(3^n)

**Note:** The closed form solution T(n) = 3^n is not always unique and depends on the form of the recurrence equation, the initial conditions and the characteristic equation. Also, this method is only applicable to the linear homogeneous recurrences with constant coefficients.

**9.Show the asymptotic notations used for best case ,average case and worst case analysis of algorithms and an algorithm for finding maximum element of an array perform best , worst and average case complexity with appropriate order notations**

Asymptotic notations are used to describe the performance of an algorithm as the input size increases. There are three main types of asymptotic notations used for the analysis of algorithms:

**Best-case complexity**: describes the best-case scenario, where the algorithm performs at its fastest. This is usually represented using big O notation, O(n)**Worst-case complexity**: describes the worst-case scenario, where the algorithm performs at its slowest. This is usually represented using big Omega notation, Ω(n)**Average-case complexity**: describes the average performance of the algorithm for all inputs. This is usually represented using big Theta notation, Θ(n)

An example of an algorithm for finding the maximum element of an array is the linear search algorithm. The linear search algorithm searches for the maximum element by iterating through the array one element at a time. The best-case scenario occurs when the maximum element is the first element in the array, in this case the best-case complexity is O(1). The worst-case and average-case scenarios occur when the maximum element is the last element in the array, in this case the worst-case and average-case complexities are both O(n).

To be more accurate, the best-case scenario would be when the maximum element is the first element in the array, the worst-case scenario would be when the maximum element is the last element in the array and the average case would be when the maximum element is at any other position in the array.

In summary, Best-case: O(1), Worst-case: O(n) and Average-case: Θ(n)

It’s worth noting that these complexities are assuming that the array is unsorted. If the array is sorted, we could use a different approach like binary search and the complexities would be different.

**10.Infer on a general review on Math needed for algorithm**

Mathematics plays a crucial role in the study of algorithms, as it provides the theoretical foundation for understanding and analyzing algorithm efficiency. Some of the key areas of mathematics used in algorithm design and analysis include:

**Discrete mathematics**: Discrete mathematics provides the basic mathematical structures and concepts used in computer science, such as sets, relations, functions, and graph theory. These concepts are used in the design and analysis of algorithms for combinatorial problems, such as sorting, searching, and graph algorithms.**Combinatorics**: Combinatorics is the branch of mathematics that deals with counting, arranging, and analyzing the possible outcomes of a given problem. It provides the tools for understanding the time and space complexity of algorithms and for analyzing the efficiency of different algorithms for a given problem.**Number theory**: Number theory is the branch of mathematics that deals with the properties of integers, including prime numbers and modular arithmetic. Number theory concepts are used in the design and analysis of algorithms for problems that involve integers, such as cryptography and hashing.**Probability and Statistics**: Probability and statistics provide a framework for understanding the average-case behavior of algorithms, as well as for modeling and analyzing random processes. It helps to analyze the average case complexities of the algorithm.**Calculus**: Calculus provides the mathematical tools for understanding the rate of change of a function, which can be used to understand the behavior of an algorithm as the input size increases. It also helps to analyze the best and worst case complexities of the algorithm.

In summary, mathematics is essential to the study of algorithms and provides the foundation for understanding and analyzing algorithm efficiency. It covers various branches of mathematics like Discrete mathematics, Combinatorics, Number theory, Probability and Statistics, Calculus which are used to understand the behavior of an algorithm, analyze the time and space complexities and make predictions

**You might be interested:**

**11. Define divide and conquers. Tabulate its pros and cons.**

Divide and Conquer is a general algorithmic technique that involves breaking down a problem into smaller sub-problems and solving each sub-problem independently. Once all the sub-problems have been solved, the solutions are combined to form a solution to the original problem. This technique is used to solve a wide variety of problems, including sorting, searching, and graph algorithms.

The following are the pros and cons of the divide and conquer technique:

**Pros:**

- Easier to understand: By breaking down the problem into smaller sub-problems, the divide and conquer technique makes it easier to understand the problem and its solution.
- Easier to implement: By solving the sub-problems independently, it is often easier to implement a divide and conquer algorithm than a more complex algorithm.
- Faster: Divide and conquer algorithms can often solve problems faster than other algorithms, particularly for large inputs.
- Parallelizable: Divide and conquer algorithms can often be easily parallelized, which can further speed up their execution.
- Reusable: The solutions to the sub-problems can often be reused in other problems, which can save time and effort in solving those problems.

**Cons:**

- Overhead: Divide and conquer algorithms can have a significant overhead due to the need to divide the problem into sub-problems and combine the solutions.
- Memory requirements: Divide and conquer algorithms can require a significant amount of memory to store the sub-problems and their solutions.
- Not always applicable: Divide and conquer techniques may not always be applicable to a given problem.

In summary, Divide and Conquer is a general algorithmic technique that breaks down a problem into smaller sub-problems

**12. Tell about divide and conquer strategy and state the general plan for working of divide and conquer algorithm.**

Divide and Conquer is a general algorithmic strategy for solving problems by breaking them down into smaller sub-problems that can be solved independently. It is a powerful technique that can be used to solve a wide range of problems, including sorting, searching, and graph algorithms.

The general plan for working of divide and conquer algorithm is as follows:

**Divide:**The first step is to divide the problem into smaller sub-problems that can be solved independently. The size of the sub-problems should be chosen such that they are small enough to be solved efficiently, but not so small that the overhead of dividing the problem becomes excessive.**Conquer**: Next, the sub-problems are solved independently. This step is often recursive, where the sub-problems are further divided into even smaller sub-problems and solved recursively.**Combine**: Finally, the solutions to the sub-problems are combined to form a solution to the original problem. This step is often straightforward and is used to combine the solutions of the sub-problems into a single solution.

Divide and conquer algorithms are often efficient as they break down a problem into smaller sub-problems and solve them independently. This makes it easier to understand the problem and its solution, and it makes it easier to implement the algorithm. Furthermore, these algorithms are often faster than other algorithms, and the solutions to the sub-problems can often be reused in other problems, which can save time and effort in solving those problems.

It’s worth noting that the divide and conquer strategy is not always applicable to all types of problems, and it can have a significant overhead due to the need to divide the problem into sub-problems and combine the solutions.

**13. Differentiate between Substitution and Tree method with computing problem**

Substitution method and tree method are two techniques used to solve recurrence relations, which are equations that describe the relationship between the value of a function at different inputs. Both of these methods are used to find the closed form solution of the recurrence relation, which is an explicit formula for the function in terms of the input.

**The substitution method** is a technique that involves assuming a general solution for the recurrence relation and then using mathematical induction to prove that the solution holds for all inputs. This method starts by assuming that T(n) = cn^k for some constants c and k, then substitute this into the recurrence relation and see if it holds true for all values of n. This method is suitable when the recurrence relation is linear, homogeneous and has constant coefficients.

**The tree method**, also known as the recursion tree method, is a technique that involves representing the recurrence relation as a tree, where each node represents a sub-problem. This method starts by drawing a tree where each level of the tree represents a call to the recurrence relation, and the number of nodes at each level represents the number of sub-problems. This method is suitable when the recurrence relation is non-homogeneous and has variable coefficients.

For example, consider the problem of computing the nth Fibonacci number using the recurrence relation F(n) = F(n-1) + F(n-2) with F(1) = F(2) = 1

**14. Contrast the number of key comparisons made in merge and quick sort.**

Merge sort and quick sort are two popular sorting algorithms that differ in the way they divide and conquer the input array.

In **merge sort**, the input array is divided into two equal-sized sub-arrays, which are then recursively sorted. The sorted sub-arrays are then merged back together to form the final sorted array. The number of key comparisons made by merge sort is proportional to the number of inversions in the input array. In the best case scenario, the array is already sorted, and the number of key comparisons is O(n*log(n)), and in the worst case scenario, the array is sorted in reverse order, and the number of key comparisons is O(n*log(n)).

In **quick sort**, the input array is divided into two sub-arrays around a pivot element. The pivot element is chosen such that all elements smaller than it are in one sub-array and all elements larger than it are in the other sub-array. The sub-arrays are then recursively sorted. The number of key comparisons made by quick sort is highly dependent on the choice of pivot element. In the best case scenario, the pivot element is the median of the input array, and the number of key comparisons is O(n*log(n)), and in the worst case scenario, the pivot element is the smallest or largest element in the input array, and the number of key comparisons is O(n^2).

In summary, Merge sort makes O(n*log(n)) key comparisons in best and worst

**15. Experiment the general divide and conquer recurrence for multiplying large integers**

The general divide and conquer recurrence for multiplying large integers is as follows:

Let X and Y be two n-digit integers represented as arrays x and y. Let n = length(x) = length(y).

We can divide X and Y into two equal-sized integers X1 and X2, and Y1 and Y2, respectively.

The product of X and Y can then be computed using the following recurrence:

Z = X*Y = 10^(2n)*Z1 + 10^n*(Z2-Z1-Z3) + Z3

where Z1 = X1*Y1 Z2 = (X1+X2)*(Y1+Y2) Z3 = X2*Y2

The recurrence uses three recursive calls to multiply the two smaller integers X1 and Y1, (X1+X2) and (Y1+Y2) and X2 and Y2 respectively.

This general divide and conquer recurrence can be implemented using a recursive algorithm that repeatedly divides the input integers into smaller sub-problems until the sub-problems are small enough to be solved directly. The solutions to the sub-problems are then combined to form a solution to the original problem.

In this case, the time complexity of the algorithm will be O(n^log(3)) which is faster than the traditional method of multiplying large integers, which has a time complexity of O(n^2).

It’s worth noting that the Karatsuba algorithm is an example of an algorithm that uses the divide and conquer technique to multiply large integers. The Karatsuba algorithm uses the same general divide and conquer recurrence as above, but also uses several optimizations to improve the performance of the algorithm.

**16. Solve the recurrence relation for merge sort using input elements**

The merge sort algorithm is a divide and conquer algorithm that sorts an input array by recursively dividing the array into two equal-sized sub-arrays and then merging the sub-arrays back together in sorted order. The time complexity of merge sort can be represented by the following recurrence relation:

T(n) = 2T(n/2) + n

where T(n) is the time complexity of sorting an input array of size n and n is the number of elements in the array. The base case for the recurrence is T(1) = 0, since sorting a single element array takes 0 time.

To solve this recurrence relation, we will use the method of solving recurrence relation using the substitution method. We will assume that the solution to the recurrence is of the form T(n) = cn*log(n) and substitute it into the recurrence relation to see if it holds true.

T(n) = 2T(n/2) + n = 2(c*(n/2)*log(n/2)) + n = cn*log(n/2) + n = cn*log(n) – cn*log(2) + n

To make the right side of the equation equal to the left side, we need to set c*log(2) = 1. Therefore, c = 1/log(2).

T(n) = n*log(n)

Hence, the time complexity of merge sort is O(n*log(n)) which means that the merge sort algorithm takes O(n*log(n)) time to sort an array of n elements.

It’s worth noting that this recurrence relation assumes that the merge step takes O(n) time. In practice, the merge step can take less than O(n) time if implemented using certain techniques like using a sentinel value

**17. Show the intermediate steps when the numbers 123, 23, 1, 43, 54, 36, 75, 34 are sorted using merge sort**

Here are the intermediate steps when the numbers 123, 23, 1, 43, 54, 36, 75, 34 are sorted using merge sort:

- Initial input array: [123, 23, 1, 43, 54, 36, 75, 34]
- Divide the input array into two equal-sized sub-arrays:
- Left sub-array: [123, 23, 1, 43]
- Right sub-array: [54, 36, 75, 34]

- Recursively sort the left and right sub-arrays:
- Left sub-array: [1, 23, 43, 123]
- Right sub-array: [34, 36, 54, 75]

- Merge the two sorted sub-arrays to form the final sorted array: [1, 23, 34, 36, 43, 54, 75, 123]

Note that in each step the array is divided in two and recursively sorted and then the two sorted arrays are merged in a sorted order.

It’s worth noting that the implementation details of the merge step can vary, but the overall process of dividing the input array into smaller sub-arrays and recursively sorting and merging them remains the same.

**18. Calculate 2101 x 1130 by using the Strassen’s matrix multiplication**

**Strassen’s matrix multiplication** is an efficient algorithm for multiplying two matrices. It is based on the divide-and-conquer strategy and reduces the number of multiplications needed by using a set of 7 matrix multiplications instead of the 8 matrix multiplications used in the standard matrix multiplication algorithm.

To calculate 2101 x 1130 using Strassen’s matrix multiplication, we first need to divide the matrices into 4 sub-matrices of equal size, following is the process:

- Divide the first matrix A into 4 sub-matrices: A11, A12, A21, and A22.
- Divide the second matrix B into 4 sub-matrices: B11, B12, B21, and B22.

Next, we will calculate 7 matrix multiplications as follows:

- S1 = B12 – B22
- S2 = A11 + A12
- S3 = A21 + A22
- S4 = B21 – B11
- S5 = A11 + A22
- S6 = B11 + B22
- S7 = A12 – A22

Then we calculate the 4 sub-matrices of the result matrix C as follows:

- C11 = S6 x S5 + S4 x S2
- C12 = S1 x A11 + S2 x B22
- C21 = S3 x B11 + S4 x A22
- C22 = S5 x S6 + S1 x S7

Finally, we combine the sub-matrices C11, C12, C21, and C22 to form the final result matrix C:

C = [C11, C12], [C21, C22]

It’s worth noting that the size of the matrices must be a power of 2 for Strassen’s matrix multiplication algorithm to be applied. If the matrices are of different sizes, they need to be padded with zeroes to make them a power of 2 before applying the algorithm.

**19. Illustrate the master theorem with sample values.**

The master theorem is a tool used to determine the asymptotic complexity of a recursive algorithm. It gives a closed-form solution to the recurrence relation of the form T(n) = aT(n/b) + f(n) where a, b, and f(n) are positive functions.

**The master theorem** states that if the recurrence relation can be written in the form T(n) = aT(n/b) + f(n) where a, b, and f(n) are positive functions, then the asymptotic complexity of the algorithm is given by one of the following cases:

- If f(n) = O(n^logb_a-ε) for any ε > 0, then T(n) = O(n^logb_a)
- If f(n) = O(n^logb_a) then T(n) = O(n^logb_a log n)
- If f(n) = O(n^logb_a+ε) for any ε > 0, then T(n) = O(f(n))

**Case 1:** Let’s consider the recurrence relation T(n) = 2T(n/2) + n^2 a = 2, b = 2, f(n) = n^2 log2 2 = 1

As f(n) = n^2 = O(n^2) which is same as n^(log2 2) = n^1 and thus T(n) = O(n^log2 2) = O(n)

**Case 2:** Let’s consider the recurrence relation T(n) = 2T(n/2) + n a = 2, b = 2, f(n) = n log2 2 = 1

As f(n) = n = O(n) which is same as n^(log2 2) = n^1, this case applies and thus T(n) = O(n^log2 2* log n) = O(n* log n)

**Case 3:** Let’s consider the recurrence relation T(n) = 2T(n/2) + n^3 a = 2, b = 2, f(n) = n^3 log2 2 = 1

As f(n) = n^3 = O(n^3) which is same as n^(log2 2 + 1) = n^(1+1) and thus T(n) = O(f(n)) = O(n^3)

It’s worth noting that the above examples are just to illustrate the theorem and in practice, we usually use the theorem when the recurrence relation is complex.

**20. Summarize the ways in which recurrence is implemented?**

**Recurrence relations **are often used to represent the time complexity of a recursive algorithm. There are several ways to implement and solve recurrence relations, including:

**Iteration**: This method involves iteratively applying the recurrence relation to find the value of the function at a specific input. This method is suitable for simple recurrence relations that can be solved by direct substitution.**Substitution**: This method involves assuming a general solution for the recurrence relation and then using mathematical induction to prove that the solution holds for all inputs. This method is suitable for linear recurrence relations with constant coefficients.**Recursion Tree**: Also known as the tree method, this technique involves representing the recurrence relation as a tree, where each node represents a sub-problem. This method is suitable for non-homogeneous recurrence relations with variable coefficients.**Master Theorem:**This method is a tool used to determine the asymptotic complexity of a recursive algorithm. it gives a closed-form solution to the recurrence relation of the form T(n) = aT(n/b) + f(n) where a, b, and f(n) are positive functions.**Dynamic programming:**Dynamic programming is a method of solving problems by breaking them down into smaller sub-problems and storing the solutions to these sub-problems to avoid redundant computation. This technique is often used to solve problems that have overlapping sub-problems.**Memoization:**Memoization is a technique used to optimize recursive functions by storing the results of expensive function calls and returning the cached result when the same inputs occur again.

All these methods are widely used to solve a variety of problems and the choice of method depends on the problem, ease of implementation and the computational cost.

Thanks for reading! 🙂

**You might be interested:**