Recursion (with examples)

Posted by zplits on Mon, 07 Feb 2022 21:03:07 +0100

1. What is recursion

A function call itself is recursive. Recursion usually transforms a large and complex problem into subproblems layer by layer until the subproblem can be solved without further recursion. Recursion greatly reduces the amount of code.

Generally speaking, a recursive algorithm consists of the following parts:

  • A termination scheme that can generate an answer without recursion
  • All other cases can be split into basic cases

2. Recursive function

Recursive algorithm plays an important role in the following cases:

  • Data is defined recursively
  • The solution of the problem is realized by recursive algorithm
  • The structural form of data is defined recursively

3. Application of recursion

To help you have a better understanding of recursion, use the three cases of recursion above

3.1 when data is defined recursively

The most common is Fibonacci sequence

F0 = 0 ; F1 = 1; Fn = Fn-1 + Fn-2 , n ≥ \geq ≥ 2

For example, we now require the fifth number of Fibonacci sequence. According to the recursive components mentioned above, we divide the components of recursive functions into two parts:

  • Return 0 when the passed in argument is 0, and 1 when the passed in argument is 1 (termination scheme)
  • In other cases, functions call themselves recursively (split)

[the external link image transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the image and upload it directly (IMG flwueed-1644237481659) (C: \ users \ lemer \ desktop \ Digui. PNG)]

The recursive algorithm will gradually split f(5) into f (1) + F (0) + F (1) + F (1) + F (0) + F (1) + F (0) + F (0) + F (1)

The program is as follows:

#include<iostream>
using namespace std;

int Fibonacci(int n) {
	if (n == 0)	//Termination scheme
		return 0;  
	if (n == 1)	//Termination scheme
		return 1;  
	return Fibonacci(n - 1) + Fibonacci(n - 2); //split
}

int main() {
	cout << Fibonacci(5);
	return 0;
}

Recursive algorithm

Think about the following questions: put M identical apples on N identical plates, and allow some plates to remain empty. How many different methods are there? (5, 1, 1 and 1, 5, 1 are the same method.)

Problem analysis:

1. Determining parameters: the valid data of the topic is only apples and plates. When constructing the recursive function, it is advisable to set m apples in n plates, and the total number of methods is f(i, j)

2. Termination scheme: (1) when there are no apples, all plates are empty and return 1; (2) Returns 0 when there is no plate

3. Split: (1) when the number of apples m is less than the number of plates n, there must be empty plates, and the number of empty plates is at least n - m. the result of this case is the same as putting m apples into m plates; (2) When the number of apples m is greater than or equal to the number of plates n, there are two cases: there are empty plates and no empty plates. Indicates that there are empty plates. You can reduce the number of plates by 1 during recursion, but the number of apples remains the same; It means that there is no empty plate. You can put an apple in each plate first, and then the number of plates remains the same when calling the function recursively, but the number of apples becomes the original number of apples minus the number of plates.

The program is as follows:

#include<iostream>
using namespace std;

int f(int m, int n) {
	if (m == 0) //Termination scheme
		return 1;
	if (n == 0) //Termination scheme
		return 0;
	if (n > m) //split
		return f(m, m);
	return f(m, n - 1) + f(m - n, n); //split
}

int main() {
	int m, n;
	cin >> m >> n;
	cout << f(m, n) << endl;	
	return 0;
}

3.3 when the data structure is defined recursively

Such as traversal of binary tree, breadth first search and so on

Now, given a binary tree, it is required to find its maximum depth. The depth of the binary tree is the number of nodes on the longest path from the root node to the farthest leaf node.

Problem analysis:

(1) Termination condition: the traversed node is an empty node

(2) Split: traverse the left and right subtrees of the current node. Add one to the depth of each traversal, and take the maximum depth of the left and right subtrees for each return value

#include<iostream>
using namespace std;

typedef struct BTree
{
    int    value;
    struct BTree* lchild;
    struct BTree* rchild;
}BTree;

BTree* CreateBTree(BTree* node, int* num, int& index)
{
    if (num[index] == 0)
        return NULL;
    else
    {
        node = new BTree;
        node->value = num[index];
        node->lchild = CreateBTree(node->lchild, num, ++index);
        node->rchild = CreateBTree(node->rchild, num, ++index);
    }
    return node;
}
void preOrder(BTree* root)
{
    if (root == NULL)
        return;
    cout << root->value << " ";
    preOrder(root->lchild);       //Recursive left subtree
    preOrder(root->rchild);       //Recursive right subtree   
}
int getdepth(BTree* root)
{
    if (root == NULL) //Termination conditions
        return 0;
    int lchild_depth = getdepth(root->lchild);
    int rchild_depth = getdepth(root->rchild);
    return max(lchild_depth, rchild_depth) + 1; //split
}
int main()
{
    int num[] = { 1,2,4,8,0,0,9,0,0,5,10,0,0,11,0,0,3,6,12,0,0,13,0,0,7,14,0,0,15,0,0 };
    BTree* root = NULL;
    int index = 0;
    root = CreateBTree(root, num, index);
    cout << "Preorder traversal is:\n";
    preOrder(root);
    cout << endl;
    cout << "The maximum depth is: ";
    cout << getdepth(root);
    return 0;
}

4. Advantages, disadvantages and optimization of recursion

4.1 advantages

In some specific problems, recursion is more concise than loop. Recursion is like a black box. When writing, you only need to pay attention to boundary conditions and splitting formulas

4.2 disadvantages

(1) Recursion is a function call, which takes up stack space and consumes a lot of time and space

(2) If the recursive function is not memorized, there will be a lot of repeated operations, wasting time and space

(3) The call stack may overflow

4.3 optimization (memorization)

We see the example code in 3.1. When n takes a large value, the program will make repeated calculations for many times. At this time, we can use the idea of space for time to record the calculated value in a table. During each operation, first find out whether the value has been calculated in the table. If there is a value calculated before, if not, store it in the table

The code implementation:

class Solution {
public:
    unordered_map<int, int>hash;  //Space for time
    int fib(int n) {
        if(n == 0) return 0;
        if(n == 1) return 1;
        if(hash[n]) return hash[n];  //Find out whether it has been calculated before calculation
        return hash[n] = fib(n - 1) + fib(n - 2); //If not, save the calculated value
    }
};

5. Summary

After reading the words here, I believe you have a better understanding of recursion. Here are two topics that can help you deepen your understanding of recursion:

Force buckle 70 to climb stairs; Maximum depth of force buckle 104 binary tree

That's all for today's sharing. See you next time!

Topics: C++ Algorithm data structure stack