递归、搜索与回溯——递归

💁‍♂️个人主页:进击的荆棘

👇作者其它专栏:

《数据结构与算法》《算法》《C++起始之路》


目录

1.总结

2.相关题解


1.总结

在解决一个规模为n的问题时,若满足一下条件,我们可以使用递归来解决:

a.问题可以被划分为规模更小的子问题,并且这些子问题具有与原问题相同的解决问题。

b.当我们知道规模更小的子问题(规模为n-1)的解时,我们可以直接计算出规模为n的问题的解。

c.存在一种简单情况,或者说当问题的规模足够小时,我们可以直接求解问题。

一般的递归求解过程如下:

a.验证是否满足简单情况。

b.假设较小规模的问题已经解决,解决当前问题。

上述步骤可以通过数学归纳法来证明。

2.相关题解

1汉诺塔问题

算法思路:

这是一道递归方法的经典题目,我们可以先从最简单的情况考虑:

●假设n=1,只有一个盘子,很简单,直接把它从A中拿出来,移到C上;

●若n=2呢?这时候就需要借助B,因为小盘子必须在大盘子上面,共需要3步(为方便叙述,记A中的盘子从上到下为1号,2号):

a.1号盘子放到B上;

b.2号盘子放到C上;

c.1号盘子放到C上。

至此,C中的盘子从上到下为1号,2号。

●若n>2呢?这时我们需要用到n=2时的策略,将A上面的两个盘子挪到B上,再将最大的盘子挪到C上,最后将B上的小盘子挪到C上就完成了所有步骤。当n=3时如下:

因为A中最后处理的是最大的盘子,所以再移动过程中不存在大盘子在小盘子上面的情况。

则本题可以被解释为:

1.对于规模为n的问题我们需要将A柱上的n个盘子移动到C柱上。

2.规模为n的问题可以被拆分为规模为n-1的子问题:

a.将A柱上面的n-1个盘子移动到B柱上。

b.将A柱上的最大盘子移动到C柱上,然后将B柱上的n-1个盘子移动到C柱上。

3.当问题的规模变为n=1时,即只有一个盘子时,我们可以直接将其从A柱移动到C柱。

●需要注意的是,步骤2.b考虑的是总体问题中的子问题b情况。在处理子问题的子问题b时,我们应将A柱中最上面的盘子移动到C柱,然后再将B柱上的盘子移动到C柱。在处理总体问题的子问题b时,A柱中的最大盘子仍然是最上面的盘子,因此这种做法是通用的。

算法流程:

递归函数设计:void dfs(vector<int>& a, vector<int>& b, vector<int>& c,int n)

1.返回值:无;

2.参数:三个柱子上的盘子,当前需要处理的盘子个数(当前问题规模)。

3.函数作用:将A上面的n个盘子挪到C上。

递归函数流程:

1.当前问题规模为n-1时,直接将A中的最上面盘子挪动到C中并返回;

2.递归将A中最上面的n-1个盘子挪动到B中;

3.将A中最上面的一个盘子挪动到C中;

4.将B中上面的n-1个盘子挪动到C中。

cpp 复制代码
class Solution {
public:
    void hanota(vector<int>& a, vector<int>& b, vector<int>& c) {
        dfs(a,b,c,a.size());
    }
    void dfs(vector<int>& a, vector<int>& b, vector<int>& c,int n){
        //出口
        if(n==1){
            c.push_back(a.back());
            a.pop_back();
            return ;
        }
        //将a上的n-1个盘子借助c移到b上
        dfs(a,c,b,n-1);
        c.push_back(a.back());
        a.pop_back();
        //将b上的n-1个盘子借助a移到c上
        dfs(b,a,c,n-1);
    }
};

2.合并两个有序链表

算法思路:

1.**递归函数的含义:**有两链表的头节点,把它们合并起来,并返回合并后的头节点;

2.**函数体:**选择两个头节点中较小的节点作为最终合并后的头节点,然后将剩下的链表交给递归函数去处理;

3.递归出口:当某一链表尾空时,返回另一链表。

cpp 复制代码
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
        if(!list1) return list2;
        if(!list2) return list1;
        if(list1->val<=list2->val) {
            list1->next=mergeTwoLists(list1->next,list2);
            return list1;
        }
        else {
            list2->next=mergeTwoLists(list1,list2->next);
            return list2;
        }
    }
};

3.反转链表

算法思路:

1.**递归函数的含义:**交给你一个链表的头指针,逆序后,返回逆序后的头节点;

2.**函数体:**先把当前节点之后的链表逆序,逆序完之后,把当前节点添加到逆序后的链表后面即可;

3.**递归出口:**当前节点为空或者当前只有一个节点的时候,不用逆序,直接返回。

cpp 复制代码
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        if(!head||!head->next) return head;
        //将此节点后面的链表逆置
        ListNode* newhead=reverseList(head->next);
        head->next->next=head;//后面的节点指向它
        head->next=nullptr;//它指向空,方便将逆置后的尾指向空

        return newhead;
    }
    
};

4.两两交换链表中的节点

算法思路:

1.**递归函数的含义:**有一链表,将这个链表两两交换,然后返回交换后的头节点;

2.**函数体:**先去处理一下第二个节点往后的链表,然后再把当前的两个节点交换,连接上后面处理后的链表;

3.**递归出口:**当前节点为空或者当前只有一个节点的时候,不要交换,直接返回。

cpp 复制代码
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        if(!head||!head->next) return head;

        //先把第三个往后的节点交换
        ListNode* tmp=swapPairs(head->next->next);
        ListNode* ret=head->next;
        head->next=tmp;
        ret->next=head;

        return ret;
    }
};

5.Pow(x, n)

算法思路:

1.**递归函数的含义:**求出x的n次方是多少,然后返回;

2.函数体:先求出x的n/2次方是多少,然后根据n的奇偶,得出x的n次方是多少;

3.**递归出口:**当n为0的时候,返回1即可。

cpp 复制代码
class Solution {
public:
    double myPow(double x, int n) {
        //return n<0?dfs(x,-n):dfs(x,n);
        //当n为无穷小时,变成正数时会存不下,强转为long long
        return n<0?1.0/dfs(x,-(long long)n):dfs(x,n);
    }
    double dfs(double x,long long n){
        if(n==0) return 1.0;
        double tmp=dfs(x,n/2);
        return n%2==0?tmp*tmp:tmp*tmp*x;
    }
};
相关推荐
可编程芯片开发4 小时前
UPFC统一潮流控制器的simulink建模与仿真
算法
小小仙子5 小时前
矢量网络分析仪如何测试S参数的?
人工智能·算法·机器学习
中微极客7 小时前
降维算法75倍加速:从PCA到稀疏字典学习的工程实践
人工智能·学习·算法
storyseek7 小时前
前缀和实现Kogge-Stone算法
数据结构·算法
元Y亨H7 小时前
开发者必须掌握的十大核心算法
算法
元Y亨H7 小时前
深度解构:数据结构与算法的理论基石与工程演进
数据结构·算法
元Y亨H8 小时前
数据结构与算法的通俗指南
数据结构·算法
2301_764441338 小时前
用动力学系统(微分方程)为 Kernberg 的客体关系单元提供数学化的操作定义,把“自体—客体“这对心理结构建模成一个二维耦合系统
数据结构·python·算法·数学建模
林泽毅8 小时前
PyTRIO快速入门(二):Datum构建
人工智能·算法·产品
keep intensify8 小时前
最长有效括号
算法·leetcode·动态规划