Time complexity analysis of recursive algorithms helps determine how execution time grows as input size increases using recurrence relations and Big O notation. Learn how to solve recurrence relations using the substitution method, recursion tree method, and Master theorem with practical examples. Understand recursive functions in C, base cases, and techniques for analyzing algorithm efficiency in Data Structures and Algorithms (DSA).
Analyzing the time complexity of recursive algorithms is an important part of Data Structures and Algorithms (DSA). It helps programmers understand how an algorithm’s execution time changes as the input size increases. For students learning C programming, embedded software development, and algorithm design, understanding recursion and recurrence relations is essential for writing efficient programs.
In this tutorial, we will explore how to analyze a loop that repeatedly squares a variable, understand recursive functions, derive recurrence relations, and calculate time complexity using the substitution method. We will also introduce other techniques, including the recursion tree method and Master theorem, with practical examples.
Time complexity in DSA describes how the number of operations performed by an algorithm grows with the input size (n). It is commonly expressed using asymptotic notations such as Big O notation.
Some common time complexities are:
For recursive algorithms, we often use recurrence relations to represent the work performed by a function and its recursive calls.
Consider the following C-style loop:
for (i = 5; i <= n; i = i * i) {
printf("IIES");
}In this loop, the variable i is squared in every iteration instead of being incremented by a constant value.
Let’s examine the sequence of values:
The loop stops when i becomes greater than n.
After repeated squaring, the values of i follow this pattern:
After k squaring operations, the value is:
The loop condition is:
Taking logarithms with base 5:
Taking logarithms with base 2 again:
Therefore, the number of iterations grows approximately as:
This is a double logarithmic time complexity, which grows very slowly as the input size increases.
The loop executes with i = 5 and i = 25. After that, i becomes 625, and the condition fails.
Therefore, the printf() statement executes 2 times.
The sequence is:
The loop executes for i = 5 and i = 25. When i becomes 625, the loop terminates.
Therefore, the printf() statement executes 2 times.
For n ≥ 5, the exact number of iterations can be expressed as:
This formula accounts for the initial iteration at i = 5, assuming exact arithmetic and the stated loop condition.
A recursive function is a function that calls itself to solve a smaller version of the same problem. Every correctly designed recursive function needs a base case to stop the recursive calls.
Consider the following C function:
int fun(int n) {
if (n <= 1)
return 1;
else
return fun(n / 2) + n;
}In this example:
n <= 1, the function returns 1.n > 1, the function calls fun(n / 2).n is added to the returned result.For example, when n = 8, the recursive calls follow this sequence:
The base case is reached when the argument becomes 1.
A recurrence relation is a mathematical equation that describes the running time of a recursive algorithm in terms of the running time for smaller inputs.
To analyze a recursive function, we represent its running time using T(n).
For the function above, each call performs constant-time work apart from the recursive call. The recurrence relation is:
Our actual example includes the operation fun(n / 2) + n. The + n in the C statement represents an arithmetic value being added; it does not mean the program performs n separate operations. Counting the work needed for the addition and other constant-time operations still gives the same recurrence, so the time complexity of this particular function is O(log n).
Several techniques are used for solving recurrence relations and finding the time complexity of recursive algorithms.
Each technique has its own advantages and limitations. The best method depends on the form of the recurrence relation.
The substitution method in recurrence relations solves a recurrence by repeatedly expanding it, observing the resulting pattern, and determining the final complexity.
Consider the following recurrence:
This is a different recurrence from the earlier C function. Here, the term +n represents linear work performed at each recursive level.
Assume the base condition is T(1) = 1.
Start with:
Substitute T(n/2) into the equation:
Expand again:
After repeating this process k times:
This is the iterative expansion of the recurrence relation.
The recursion stops when:
Therefore:
Taking logarithms:
The number of recursive levels is approximately log2 n.
The expanded recurrence contains the series:
This is a geometric progression (GP) with first term a = n and common ratio r = 1/2.
For a finite geometric progression where r ≠ 1:
Substituting a = n and r = 1/2:
Simplifying:
Since 2k = n when n is a power of two:
Including the base-case cost T(1) = 1:
Therefore:
For powers of two under the stated base-case assumption, this is the exact result. For general input sizes, the same asymptotic bound applies.
Hence, the final time complexity is:
The substitution method helps us understand how the work accumulates across recursive calls and determine the asymptotic time complexity.
These recurrence relations look similar, but their time complexities are different.
| Recurrence relation | Work at each level | Time complexity |
| T(n) = T(n/2) + n | Decreases geometrically | Θ(n) |
| T(n) = T(n/2) + 1 | Constant work | Θ(log n) |
In the first recurrence, the total work is a geometric series dominated by its first term. In the second recurrence, each level contributes constant work, and there are approximately log2 n levels.
Understanding this difference is essential when analysing recursive functions in C and other programming languages.
The substitution method is useful when you can repeatedly expand a recurrence and recognize a pattern. It works well for many basic recurrence relations used in DSA.
However, complicated recurrences may be easier to solve with a recursion tree, Master theorem, or another mathematical technique. The Master theorem, in particular, applies only to certain recurrence forms and should not be used indiscriminately.
The original ESP32 chip has up to 34 GPIOs numbered 0–19, 21–23, 25–27, and 32–39. However, some are reserved for flash, some are input-only, and not all are exposed on every development board.
On common ESP32-WROOM-32 boards, beginners should generally avoid GPIO 6–11 because they are connected to flash memory. They should also take care with boot-strapping pins such as GPIO 0, 2, 5, 12, and 15.
No. Original ESP32 GPIO pins are designed for 3.3 V logic and are not 5 V tolerant. Use a suitable voltage divider or logic-level shifter when interfacing with a 5 V signal.
An input pin reads an external digital signal, such as a push button or sensor output. An output pin drives a logic-level signal, such as switching an LED through a current-limiting resistor.
Indian Institute of Embedded Systems – IIES