The Ultimate Understanding of Recursive Algorithms—Understanding Recursive Algorithms with the Human Brain

Special statement: I read this article by accident, not my own creation. I feel that the summary and understanding of recursive algorithms have reached a very deep level. I love it very much, and everyone will appreciate it. I'm sorry for the source, because I found it on my IOS device, the URL can't be opened, so I didn't specify the source, I hope the author of the original text will forgive me!

Recursion is a wonderful way of thinking. Since I learned recursion in my second year of sophomore year, for some simple recursion problems, I have always been amazed at the simplicity of recursive description of the problem and of writing code. However, I always feel that I cannot fully understand recursion. Sometimes I try to use my brain to go deep into "recursion". When the level is deeper, I often feel that I can't get in or get out. This state also makes it difficult for me to use recursion flexibly to solve problems. One day, I saw an English sentence: "To Iterate is Human, to Recurse, Divine." The Chinese translation is: "Human understands iteration, God understands recursion." Then, I gave up my deep understanding of recursion with peace of mind. Until I saw Wang Yin talking about the most essential principles of programming language, he mentioned recursion, and said that recursion is much more expressive than loop, and the efficiency is almost the same. Awakened my understanding of recursion again.

I first found the following two examples on Zhihu, comparing recursion and loop.

Recursion : You open the door in front of you and see that there is another door in the house (this door may be the same size as the door opened in front (quiet), or it may be smaller (moving)), you walk over and find the door in your hand The key can also open it, you push the door and find there is another door inside, you continue to open it. . . , After several times, you open a door in front of you and find that there is only one room and no door. You start to go back the same way. Every time you go back to a room, you count once. When you reach the entrance, you can answer how many doors you opened with this key.

Cycle : You open the door in front of you and see that there is another door in the house, (this door may be the same size as the door opened in front (quiet), or the door may be smaller (moving)), you walk over and find that in your hand The key can also open it, you push the door and find that there is another door inside, (if the front door is the same, this door is the same, if the second door is smaller than the first door, this door is also smaller than the first door. The second door becomes smaller (the movement is the same, either no change, or the same change)), you keep opening this door,. . . , keep going like this. The person at the entrance never waits for you to go back and tell him the answer.

The user summed it up this way:

Recursion means that there is going (handing out) and returning (returning).

Specifically, why can "have to go"? 
This requires recursive problems that can be answered with the same problem-solving ideas except for the size of the other exact same problem.

Why "return"?
This requires that these problems continue to grow from big to small, from near to far, there will be an end point, a critical point, a baseline, a point where you don’t have to go to smaller and farther places when you reach that point. , and then from that point, the original path returns to the origin.

The above explanation pretty much answers my long-standing question: why do I keep feeling like recursion isn't really solving the problem?
Because transfer is to describe the problem, return is to solve the problem. And my brain is easily occupied, I only go far away, and I don't even go to the end, so why not come back.

The article "On Recursion: The Thought of Recursion" summarizes the idea of ​​recursion as:

The basic idea of ​​recursion is to transform a large-scale problem into small-scale similar sub-problems to solve. When a function is implemented, because the method of solving big problems and the method of solving small problems are often the same method, there is a situation where the function calls itself. In addition, the function that solves the problem must have an obvious end condition, so that there is no infinite recursion.

It should be noted that the core idea is to transform a large scale into a small scale, but recursion does not only do this transformation, but decomposes a large-scale problem into small-scale sub-problems and the remaining ones that can be solved on the basis of sub-problems. Parts that can be resolved on their own. The latter is the essence of the return, which is the process of actually solving the problem.

I am trying to express my understanding of recursive thinking with recursive programming, and I have determined three elements: recursion + end condition + return.

recursion(大规模)
{
     if (end_condition)
     {
          end;     
     }
     else
     {     //先将问题全部描述展开,再由尽头“返回”依次解决每步中剩余部分的问题
          recursion(小规模);     //go;
          solve;                //back;
     }
}

However, I easily find that this description misses a recursive case I often encounter, such as the preorder of a recursively traversed binary tree. I express this situation with the following recursive program.

recursion(大规模)
{
     if (end_condition)
     {
          end;     
     }
     else
     {     //在将问题转换为子问题描述的每一步,都解决该步中剩余部分的问题。
          solve;                //back;
          recursion(小规模);     //go;
     }
}

总结到这里,我突然发现递归是为了最能表达这种思想,所以用“递归”这个词,其实递归可以是“有去有回”,也可以是“有去无回”。但其根本是“由大往小地去,由近及远地去”。“递”是必需,“归”并非必需,依赖于要解决的问题,有的需要去的路上解决,有的需要回来的路上解决。有递无归的递归其实就是我们很容易理解的一种分治思想。

其实理解递归可能没有“归”,只有去(分治)的情况后,我们应该想到递归也许可以既不需要在“去”的路上解决问题,也不需要在“归”的路上解决问题,只需在路的尽头解决问题,即在满足停止条件时解决问题。递归的分治思想不一定是要把问题规模递归到最小,还可以是将问题递归穷举其所有的情形,这时通常递归的表达力体现在将无法书写的嵌套循环(不确定数量的嵌套循环)通过递归表达出来。
将这种递归情形用递归程序描述如下:

recursion()
{
     if (end_condition)
     {
          solve;     
     }
     else
     {     //在将问题转换为子问题描述的每一步,都解决该步中剩余部分的问题。
          for () { recursion();     //go; }
     }
}

例如,字符串的全排列就可以用这种递归简洁地表达出来,如下:

void permute(const string &prefix, const string &str)
{
    if(str.length() == 0)
        cout << prefix << endl;
    else
    {   
        for(int i = 0; i < str.length(); i++)
            permute(prefix+str[i], str.substr(0,i)+str.substr(i+1,str.length()));
    }   
}

由这个例子,可以发现这种递归对递归函数参数出现了设计要求,即便递归到尽头,组合的字符串规模(长度)也没有变小,规模变小的是递归函数的一个参数。可见,这种变化似乎一下将递归的灵活性大大地扩展了,所谓的大规模转换为小规模需要有一个更为广义的理解了。

对递归的理解就暂时到这里了,可以看出文章中提到关于“打开一扇门”的递归例子来解释递归并不准确,例子只描述了递归的一种情况。而“递归就是有去(递去)有回(归来)”的论断同样不够准确。要为只读了文章前半部分的读者惋惜了。我也给出自己对递归思想的总结吧:

递归的基本思想是广义地把规模大的问题转化为规模小的相似的子问题或者相似的子问题集合来解决。广义针对规模的,规模的缩小具体可以是指递归函数的参数,也可以是其参数之一。相似是指解决大问题的方法和解决小问题的方法往往是同一个方法,还可以是指解决子问题集的各子问题的方法是同一个方法。解决大问题的方法可以是由解决次规模问题的方法和解决剩余部分的方法组成,也可以是由一系列解决次规模问题的方法组成。

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324472271&siteId=291194637