再谈递归

  我们似乎记得有这么一个观点”递归都可以通过迭代(循环)实现,但是迭代不一定能够通过递归实现“。那么我们是否思考过这其中的道理呢?

一、递归概念

  对于什么是递归,众说纷纭。我个人比较认可的一种简单直接的文字描述是:先递进,再回归;还有一种是动图描述:一只兔子拿着一面镜子,镜子里面和镜子的镜子的...镜子里面还是相同的场景,循环不止。当然,递归可不能这么无限下去,容易栈溢出,所以,递归得有出口。

二、什么时候用递归

  第一次接触递归的例子是斐波拉契函数:F(n) = F(n - 1) + F(n - 2), F(1) = F(0) = 1; n >= 2。对该函数进行分析不难发现,如果我们想求解一个大的斐波拉契函数值m,我们必须得先不断递进到我们能够获取到的函数值,例如:F(1) = F(0) = 1。然后再回归,依次求解F(2) = F(1) + F(0); F(3) = F(2) + F(1); ... ....; F(m)  = F(m - 1) + F(m - 2)。

  代码如下:

1 int F(int n)
2 {
3      if(n == 0 || n == 1){
4          return 1; 
5      }
6       return F(n - 1) + F(n - 2);   
7 }

  所以,斐波拉契拥有完美的递归性,非常适合使用递归实现。此时,递归出口:n = 0 或 n = 1的时候返回1,递归条件:F(n - 1) + F(n - 2);当然迭代也能够完美实现该函数。到这里,我们就明白了,能够迭代实现的不一定拥有递归性,比如:递归出口。我们可以这么通俗的理解递归:递归是一种将复杂问题逐步分解为小的相同问题,直到分解到我们能够求解的最小问题为止,每个次小问题的解都给予更小问题的解,最后回归到复杂问题的解的方法。一般能够用数学公式表示的,可通过数学公式知道是否具有递归性;但是有时候我们的问题并不能单纯的通过数学公式表示,也可能具有递归性,比如汉诺塔。

三、哪些数据结构拥有递归性

  拥有完美递归性的数据结构里面想到的是二叉树。例如:

                      A
                   /      \
                  B       C
                /   \    /   
               D    E   F

  从根节点A看,就是一个二叉树;从A的左子节点B或右子节点C向下看都是一颗二叉树。其实,链表也拥有递归性。例如:

-----------    -----------   -----------
| A | next|--> | B | next|-->| C | null|
-----------    ------------  -----------

  当我们继续向尾部插入节点D的时候,我们可以将A --> B -->C这个链表当成一个头节点head,只有找到这个头节点,然后head->D即可。

四、二叉树和链表递归实现

  二叉树数的很多操作都可以直接用递归实现,代码逻辑简单,最典型的就是深度遍历,如下:

 1 // Root-Left-Right
 2 void PrevOrder(Node* root)
 3 {
 4     if(root == nullptr){
 5         return;
 6     }
 7     std::cout  << root->data << ",";
 8     PrevOrder(root->left);
 9     PrevOrder(root->right);
10 }
11 
12 // Left-Root-Right
13 void Inorder(Node* root)
14 {
15     if(root == nullptr){
16         return;
17     }
18     Inorder(root->left);
19     std::cout << root->data << ",";
20     Inorder(left->right);
21 }
22 
23 // Left-Right-Root
24 int PostOrder(Node* root)
25 {
26     if(root == nullptr){
27         return;
28     }
29     PostOrder(root->left);
30     PostOrder(root->right);
31     std::cout << root->data << ",";
32 }

  单链表尾插递归算法:

1 Node* PushBack(Node* head, int data)
2 {
3     // 带头结点的链表
4     if(head->next == nullptr){
5        return new Node(data);
6     }
7     head->next = PushBack(head->next, data);
8     return head;
9 }

  单链表倒置递归算法:

 1 Node* Invert(Node* head)
 2 {
 3     if(head->next == nullptr){
 4         return head;
 5     } 
 6     auto newHead = Invert(head->next); // head--> newHead
 7    
 8     newHead->next = head;  // head <--> newHead
 9     head->next = nullptr;      // head<-- newHead
10     return newHead;
11 }

  上面迭代法的逻辑也相对比较简单,感兴趣的同学可以自行实现。

  上面是我们对递归算法的个人看法,主要是方便个人回顾之用,也希望其他读者能够对递归的认识起到一点理解的作用!

posted @ 2021-05-09 22:43  blackstar666  阅读(107)  评论(0编辑  收藏  举报