我们似乎记得有这么一个观点”递归都可以通过迭代(循环)实现,但是迭代不一定能够通过递归实现“。那么我们是否思考过这其中的道理呢?
一、递归概念
对于什么是递归,众说纷纭。我个人比较认可的一种简单直接的文字描述是:先递进,再回归;还有一种是动图描述:一只兔子拿着一面镜子,镜子里面和镜子的镜子的...镜子里面还是相同的场景,循环不止。当然,递归可不能这么无限下去,容易栈溢出,所以,递归得有出口。
二、什么时候用递归
第一次接触递归的例子是斐波拉契函数: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 }
上面迭代法的逻辑也相对比较简单,感兴趣的同学可以自行实现。
上面是我们对递归算法的个人看法,主要是方便个人回顾之用,也希望其他读者能够对递归的认识起到一点理解的作用!