Algorithm analysis and design "two" recursive algorithm

1, Recursive thought

Recursion is the most commonly used method in algorithm design. It usually makes the description and solution of a large and complex problem concise and clear.

What is recursion?

In short, a function call itself is recursive. If you want n! Recursive implementation function of:

int Factorial(int n)
{
	if (n == 0)
		return 1;
	else
		return n * Factorial(n - 1);
}

Under what circumstances will recursive algorithms be used?

  • The definition is recursive. For example, find n factorial, Fibonacci sequence.
  • The solution of the problem is realized by recursive algorithm. Such as Hanoi problem.
  • The structural form of data is defined recursively. Such as binary tree, generalized table, etc.

Function of recursion

  • Replace multiple loops.
  • Solve the problem that is defined in recursive form. For example, in the following example: Polish expression and four arithmetic expressions
  • The problem is decomposed into smaller subproblems to solve. As in the following examples: Hanoi Tower, climbing stairs, putting apples, 24 o'clock

How are recursive calls implemented?

We know that the recursive call of ordinary functions is realized through the stack, and recursion, like ordinary functions, is also realized through the stack. The system arranges the data space required for the running of the whole program in a stack. Whenever a function is called, it allocates a storage area at the top of the stack. Whenever it exits from a function, it releases its storage area.

It's a little abstract. It may be easier to understand by looking at the following example. Recursively find 4! The activities of the stack are as follows:


 

2, Classic examples

Example 1: Hanoi Tower problem

Problem Description: there was a Vatican Pagoda in ancient times. There were three seats a, B and C in the pagoda. There were 64 plates on seat a. the plates were of different sizes, the large ones were at the bottom and the small ones were at the top (as shown in the figure). A monk wanted to move the 64 plates from seat a to seat C, but only one plate can be moved at a time. In the process of moving, the plates on the three seats always keep the big plate at the bottom and the small plate at the top. In the process of moving, seat B can be used to require the output of moving steps.

Basic idea: no matter how many plates, you only need to regard them as two parts, one is the top n-1 plate, and the other is the bottom plate. In this way, you only need to consider how the two plates move. Using the recursive algorithm, it will automatically help you move n plates.

Code implementation:

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;

// Move from a to c, and b is the transfer station
void Hanoi(int n, char a, char b, char c)
{
    if (n == 1)
    {
        cout << a << "->" << c << endl;
        return;
    }
    Hanoi(n - 1, a, c, b);	
    cout << a << "->" << c << endl;
    Hanoi(n - 1, b, c, a);
    return;
}

int main()
{
    int n;
    cin >> n;
    Hanoi(n, 'A', 'B', 'C');
    return 0;
}

 

Example 2: Polish expression

After learning the data structure, you should know the concepts of prefix expression, infix expression and suffix expression. The Polish expression here is the prefix expression, and the inverse Polish expression is the suffix expression.

Polish expression is an arithmetic expression that precedes the operator. For example, the inverse Polish representation of ordinary expression 2 + 3 is + 2 3. The advantage of Polish expression is that there is no priority relationship between operators, and there is no need to change the operation order with parentheses. For example, the inverse Polish representation of (2 + 3) * 4 is * + 23 4. This problem solves the value of Polish expression, in which the operators include + - * / four.

Input: the input is a line, where the operator and operand are separated by spaces, and the operand is a floating point number.

Output: output as a line, the value of the expression.

	// sample input 
	* + 11.0 12.0 + 24.0 35.0
	// sample output 
	1357.000000
	// Prompt: (11.0 + 12.0) * (24.0 + 35.0)

Code implementation:

#include <iostream>
#include <cstdio>
#include <cstdlib>
using namespace std;

double exp()
{
    //Read in a Polish expression and calculate its value
    char s[20];
    cin >> s;
    switch (s[0])
    {
    case '+':
        return exp() + exp();
    case '-':
        return exp() - exp();
    case '*':
        return exp() * exp();
    case '/':
        return exp() / exp();
    default:
        return atof(s); // atof() converts a floating point number to double
        break;
    }
}

int main()
{
    printf("%lf", exp());
    return 0;
}

 

Example 3: evaluation of four operation expressions

Title Description: the input is four operation expressions, which are only composed of integers, +, -, *, /, (,) without spaces. It is required to find their values. Assume that the operator results are integers. The "/" result is also an integer.

Solution: expression is a recursive definition. The expression is obtained by adding and subtracting the term, the term is obtained by multiplying and dividing the factor, and the factor is the expression. As shown in the figure below:


// input
(2+3)*(5+7)+9/3
// output
63

Implementation code:

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;

int factor_value();	
int term_value();
int expression_value();

int main()
{
  cout << expression_value() << endl;
  return 0;
}

// Evaluate an expression
int expression_value()
{
  int result = term_value(); // Find the value of the first term
  bool more = true;
  while (more)
  {
    char op = cin.peek(); // View the first character in the input stream, but do not take it away
    if (op == '+' || op == '-')
    {
      cin.get();
      int value = term_value();
      if (op == '+')
        result += value;
      else
        result -= value;
    }
    else
      more = false;
  }
  return result;
}

// Find the value of the term
int term_value()
{
  int result = factor_value(); // Find the value of the first factor
  while (true)
  {
    // Determine whether there are other factors
    char op = cin.peek();
    if (op == '*' || op == '/')
    {
      cin.get();
      int value = factor_value();
      if (op == '*')
        result *= value;
      else
        result /= value;
    }
    else
      break;
  }
  return result;
}

// Find the value of the factor
int factor_value()
{
  int result = 0;
  // There are two kinds of factors, numbers or expressions
  char c = cin.peek();
  if (c == '(')
  {
    cin.get();
    result = expression_value();
    cin.get();
  }
  else
  {
    while (isdigit(c))
    {
      result = 10 * result + c - '0';
      cin.get();
      c = cin.peek();
    }
  }
  return result;
}

Let's talk about the usage of cin.peer() and cin.get(). cin.peek() returns the next character in the input stream, but it will not be read. It is only used to observe the next character, and the character is still in the input stream. cin.get will read the next character, similar to getchar() in C language.

Example 4: climbing stairs

Problem Description: Mr. tree climbs the stairs. He can walk 1 or 2 levels at a time, enter the number of stairs and find different walking methods. For example, there are three stairs in total. He can go one step at a time, or one step for the first time and two steps for the second time; You can also take two levels for the first time and one level for the second time. There are three methods in total.

Input: the input contains several lines, each line contains a positive integer N, which represents the stair level. 1 < = N < = 30 outputs different walking numbers, and each line input corresponds to one line.

Output: for different walking numbers, each line of input corresponds to one line of output.

// input
5
8
10
// output
8
34
89

Problem solving idea: walking method of N steps = (walking method of n-1 steps after one step) + (walking method of n-2 steps after two steps). It is clear that the recurrence formula of this problem is f(n) = f(n-1) + f(n-2). Next, consider the termination condition (boundary condition) of this problem to prevent recursion. Boundary conditions can be expressed in many ways. For example, when n < 0, it returns 0, and when n = 1, it returns 1.

Implementation code:

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;

int N;
int stairs(int n)
{
    if (n < 0)
        return 0;
    if (n == 0)
        return 1;
    return stairs(n - 1) + stairs(n - 2);
}

int main()
{
    while (cin >> N)
    {
        cout << stairs(N) << endl;
    }
    return 0;
}

Example 5: put apples

Question Description: put M identical apples on N identical plates, and allow some plates to remain empty. How many different methods are there? Note that here 5, 1, 1 and 1, 5, 1 are the same division.

Input: the first line is the number of test data t (0 < = T < = 20). Each of the following lines contains two integers m and N, separated by spaces. 1 <= M,N <= 10.

Output: for each set of input data M and N, use one line to output the corresponding K.

// sample input 
1
7 3
// sample output 
8

Solution idea: define the function, let I apples be placed on K plates, and the total number of placement methods is f(i,k), then: ① when k > I, f(i,k) = f(i,i) ② when k < = I, the total placement method = placement method with empty plates + placement method without empty plates, that is, f(i,k) = f(i,k-1) + f(i-k,k). In addition, considering the boundary conditions, M = 0, N = 0.

Implementation code:

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;

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

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

Example 6: 24:00

Problem Description: given four positive integers less than 10, you can use four operations of addition, subtraction, multiplication and division and parentheses to connect the four numbers to get an expression. The question now is whether there is a way to get the result of the expression equal to 24. For example, for 5, 5, 5, 1, we know that 5 * (5 – 1 / 5) = 24, so we can get 24. For example, for 1, 1, 4, 2, we can't get 24.

Input: the input data includes multiple lines, and each line gives a set of test data, including 4 positive integers less than 10. The last set of test data includes four zeros, indicating the end of input. This set of data does not need to be processed.

Output: for each group of test data, output one line. If 24 can be obtained, output YES; Otherwise, NO is output.

// sample input 
5 5 5 1
1 1 4 2
0 0 0 0
// sample output 
YES
NO

Solution idea: n numbers count 24, there must be two numbers to count first. The results of these two numbers and the remaining n-2 numbers constitute the problem of finding 24 for n-1 numbers. Therefore, the following problem is to enumerate the two numbers calculated first and the operation mode of these two numbers. Finally, considering the boundary conditions, when n = 1, you can directly judge YES or NO.

Code implementation:

#include <iostream>
#include <cmath>
using namespace std;

double a[5];
#define ESP 1e-6

bool isZero(double x)
{
    return fabs(x) <= ESP;
}

bool count24(double a[], int n)
{
    // boundary condition 
    if (n == 1)
    {
        if (isZero(a[0] - 24))
            return true;
        else
            return false;
    }
    // When boundary conditions are not satisfied
    double b[5]; // Store intermediate results
    for (int i = 0; i < n - 1; ++i)
    {
        for (int j = i + 1; j < n; ++j)
        {
            int m = 0;
            for (int k = 0; k < n; ++k)
                if (k != i && k != j)
                    b[m++] = a[k]; // Select two numbers for operation first, and the other n-2 numbers are stored in the b array
            // In this way, it turns to the problem of finding 24 for n-1 numbers
            b[m] = a[i] + a[j];
            if (count24(b, m + 1))
                return true;
            b[m] = a[i] - a[j];
            if (count24(b, m + 1))
                return true;
            b[m] = a[j] - a[i];
            if (count24(b, m + 1))
                return true;
            b[m] = a[i] * a[j];
            if (count24(b, m + 1))
                return true;
            if (!isZero(a[j]))
            {
                b[m] = a[i] / a[j];
                if (count24(b, m + 1))
                    return true;
            }
            if (!isZero(a[i]))
            {
                b[m] = a[j] / a[i];
                if (count24(b, m + 1))
                    return true;
            }
        }
    }
    return false;
}

int main()
{
    while (true)
    {
        for (int i = 0; i < 4; ++i)
            cin >> a[i];
        if (isZero(a[0]))
            break;
        if (count24(a, 4))
            cout << "YES" << endl;
        else
            cout << "NO" << endl;
    }
    return 0;
}

Tags: Algorithm recursion

Posted on Sun, 28 Nov 2021 15:33:54 -0500 by ferrit91