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

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

👇作者其它专栏:

《数据结构与算法》《算法》《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;
    }
};
相关推荐
迷途之人不知返20 小时前
Stack & Queue
c++·算法
没文化的阿浩20 小时前
【数据结构】排序(2)——直接选择排序、堆排序
数据结构·算法·排序算法
ytttr87320 小时前
基于libusb的用户空间UVC相机库
算法
bybitq20 小时前
Reactor 模型 vs Proactor 模型:区别与代码示例
算法
jimy120 小时前
C 语言的 static 关键字作用
c语言·开发语言·算法
七颗糖很甜20 小时前
基于IRI-2016模型计算电子密度、TEC、foF2等参数的技术原理与代码实现
大数据·python·算法
风筝在晴天搁浅20 小时前
LeetCode 143.重排链表
算法·leetcode·链表
碧海银沙音频科技研究院21 小时前
如何彻底关闭360壁纸
人工智能·深度学习·算法
sali-tec21 小时前
C# 基于OpenCv的视觉工作流-章57-人脸识别
图像处理·人工智能·opencv·算法·计算机视觉
计算机安禾21 小时前
【Linux从入门到精通】第43篇:I/O调度算法与磁盘性能优化
linux·算法·性能优化