In this guide, we will explore the significant tree list issue in C++, accompanied by multiple illustrations.
Introduction:
Consider a program designed to calculate the factorial of a given number. The function accepts a number N as an argument and produces the factorial of N as the output. The pseudo-code for this function will appear as follows:
Example:
#include <iostream>
using namespace std;
int sumOfNaturalNumbers(int n) {
// Base case
if (n == 1) {
return 1;
}
// Recursive step
return n + sumOfNaturalNumbers(n - 1);
}
int main() {
int n = 5; // Calculate the sum of natural numbers from 1 to 5
int result = sumOfNaturalNumbers(n);
cout << "Sum of natural numbers from 1 to " << n << " is: " << result << endl;
return 0;
}
Output:
Sum of natural numbers from 1 to 5 is: 15
Explanation:
- Recursion is exemplified by the function that was previously mentioned. We are invoking a function to determine a number's factorial . After that, this function calls itself with a lesser value of the same number. It continues until we reach the base case, in which there are no more function calls.
- Recursion is a technique for handling complicated issues when the outcome is dependent on the outcomes of smaller instances of the same issue.
- If we think about functions, a function is said to be recursive if it keeps calling itself until it reaches the base case.
- Any recursive function has two primary components: the base case and the recursive step .
- We stop going to the recursive phase once we reach the base case. Base cases must be carefully defined and are crucial to prevent infinite recursion. The definition of infinite recursion is a recursion that never reaches the base case. If a program never reaches the base case, stack overflow will continue to occur.
Recursion Types:
There exist primarily two distinct forms of recursion:
- Direct Recursion
- Indirect Recursion
1. Linear Recursion:
A function that invokes itself only once during each execution is classified as linear recursive. An excellent example of linear recursion can be seen in the factorial function. The term "linear recursion" denotes that a linearly recursive function requires a linear duration to complete its execution.
Example:
Let's take a look at the pseudo-code below:
#include <iostream>
using namespace std;
void countdown(int n) {
// Base case to stop recursion
if (n == 0) {
return;
}
// Print the current value
cout << n << " ";
// Recursive step
countdown(n - 1);
}
int main() {
int n = 5; // Countdown from 5 to 0
cout << "Countdown: ";
countdown(n);
cout << endl;
return 0;
}
Output:
Countdown: 5 4 3 2 1
In this illustration, the most basic situation occurs when n is zero. The recursive process terminates when n reaches zero. Subsequent to this stage, the function ceases to initiate further recursive invocations. The function displays the present value of n after each cycle. We can denote the printing action as a constant-time operation utilizing the symbol K.
Let's establish the recurrence relation for the time complexity at this point:
T(n) = T(n - 1) + K
- When the function is called with the value n as an argument, T(n) denotes the amount of time needed to complete the entire computation.
- The time needed for the recursive call with n - 1 is denoted by T(n - 1) .
- K stands for the fixed amount of time needed to print the value of n.
Based on this recursive formula, the duration required to run the function with input n equals the time taken for the recursive invocation with n - 1, alongside a constant time K for displaying the n value. Consequently, the function is mistakenly invoked n times.
Given that this code operates with a time complexity of O(n), the duration required for its execution is directly proportional to the input size, denoted as n. The function is invoked n times, with each invocation carrying out a consistent workload.
2. Tree Recursion:
When a recursive call is made multiple times within the recursive case, it is known as tree recursion. A classic example of this concept is demonstrated in the Fibonacci sequence. Tree recursive functions operate in exponential time, contrasting with linear temporal complexity.
Example:
Take a look at the pseudo-code below,
function sumTree(node) {
if (node === null) {
return 0; // Base case: an empty tree has a sum of 0
}
// Recursive step: sum the current node's value with the sums of its left and right subtrees
return node.value + sumTree(node.left) + sumTree(node.right);
}
// Define a simple binary tree structure
class TreeNode {
constructor(value, left = null, right = null) {
this.value = value;
this.left = left;
this.right = right;
}
}
// Create a sample binary tree
const tree = new TreeNode(1,
new TreeNode(2,
new TreeNode(3),
new TreeNode(4)
),
new TreeNode(5,
null,
new TreeNode(6)
)
);
// Calculate the sum of all values in the tree
const treeSum = sumTree(tree);
console.log("Sum of tree values: " + treeSum);
Output:
Sum of tree values: 21
Explanation:
- In this example, with the exception of one additional call to the same function with a lower value of n, this function sumtree(n) is nearly identical to the previous one.
- Let's write T(n) = T(n-1) + T(n-2) + k for this function's recurrence relation. Here, K is still another constant.
- When a function is called more than once with smaller values, this sort of recursion is known as a tree recursion .
How does the recursion tree method work?
The technique of recursion tree is applied in solving recurrence equations like T(N)=T(N/2)+N or the ones we covered earlier (linear recursion and Tree recursion) in the recursion types section. The common method employed by these recursive equations to address problems is the divide and conquer strategy.
It requires some time to combine the solutions to the smaller subtasks that emerge when breaking down a larger problem into more manageable subproblems.
For example, the recursive formula T(N)=2T(N/2)+O(N) represents the recurrence relation in Merge sort. The duration required to merge the solutions of two subtasks with a total size of T(N/2) is O(N), a fact that holds true during the practical application. During the merging phase of Merge sort, we merge two ordered arrays to generate a newly sorted array within a linear timeframe.
For example, the recursive formula for binary search is represented as T(N) = T(N/2) + 1. It is common knowledge that each iteration of a binary search reduces the search area by half. Upon reaching the result, we terminate the function. Since this action requires a fixed duration, an additional +1 is incorporated into the recursive equation.
Take into account the recurrence equation T(n)=2T(n/2)+Kn, where Kn represents the time needed to merge solutions from subproblems of dimension n/2.
Let's illustrate the recursive tree for the given recurrence relationship.
We may draw a few conclusions from studying the recursion tree above, including:
- The size of the problem at each level is all that matters when determining the value of a node. The issue size is n at level 0, n/2, and n/2 at level 1, etc.
- In general, we define the height of the tree as equal to log(n) , where n is the size of the problem, and the height of this recursion tree is equal to the number of levels in the tree. It is true because as we just established, the divide-and-conquer strategy is used by recurrence relations to solve problems, and getting from problem size n to problem size 1 only requires taking log(n)
- At each level, we consider the second term in the recurrence to be the root.
- Even though this method's name contains the word "tree" , we don't need to be an expert on trees to comprehend it.
How to Use a Recursion Tree to Solve Recurrence Relations:
The expense associated with a subproblem refers to the duration required for its resolution within the framework of the recursion tree method. Thus, when referring to the "cost" in relation to the recursion tree, it solely denotes the time required to address a subproblem.
Using the recursion tree approach, there are a few stages that must be taken to solve a recurrence relation. Including,
- Draw the recursion tree to represent the given recurrence relation.
- The recursion tree's height should be calculated.
- Calculate the cost (amount of time needed to solve each level's subproblems) for each level.
- Determine how many nodes there are overall in the recursion tree at each level.
- The recursion tree's costs are totaled across all of its tiers.
- Recursion is a technique for handling complicated issues when the outcome is dependent on the outcomes of smaller instances of the same issue.
- Linear recursion and Tree recursion are the two main types of recursion.
- The Recursion tree method is one of many approaches for solving recurrence relations.
- These recurrence relations are essentially just a mathematical definition of a recursive problem. The recursion tree method has a few stages that must be taken to solve a recurrence relation.