首页 > 编程语言 >算法妙妙屋-------1.递归的深邃回响:C++ 算法世界的优雅之旅

算法妙妙屋-------1.递归的深邃回响:C++ 算法世界的优雅之旅

时间:2024-11-03 10:46:15浏览次数:4  
标签:链表 head return 递归 C++ next ------- 算法 ListNode

前言:

递归是一种在算法中广泛应用的思想,其主体思想是通过将复杂的问题分解为更简单的子问题来求解。具体而言,递归通常包括以下几个要素:

  1. 基本情况(Base Case):每个递归算法必须有一个或多个基本情况,用于定义何时停止递归。基本情况是问题的 最小实例 ,直接返回结果,不再进行进一步的递归。

  2. 递归情况(Recursive Case):当问题不是 基本情况 时,递归算法会将问题 拆分成更小的子问题 。算法会调用自身来解决这些子问题,通常会在调用中传递参数以反映问题的简化。

  3. 合并结果(Combining Results):在递归调用返回后,算法 ***会将子问题的结果合并***,以得到原始问题的解。

递归的优势在于其代码通常更简洁且易于理解,尤其是在处理分治问题(如排序、搜索等)时。然而,递归也可能导致栈溢出问题,因为每次调用都会在栈上占用空间,因此在使用时需要考虑调用深度和性能优化。

下面,我们就用习题来给大家做解释吧!

1. 汉诺塔(easy)(非常经典)

题目链接:汉诺塔

在这里插入图片描述
递归函数流程:

  1. 当前问题规模为n=1时,直接将A中的最上⾯盘⼦挪到C中并返回;
  2. 递归将A中最上⾯的n-1个盘⼦挪到B中;
  3. 将A中最上⾯的⼀个盘⼦挪到C中;
  4. 将B中上⾯n-1个盘⼦挪到C中。

解题思路:

  • 先把n-1盘全部移到B上去

  • 再把第n盘移到C上去

  • 再把n-1盘移到C上去

代码如下:

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;
        }

         dfs(A, C, B,n-1);//这一步是为了将除了最大的盘子留下外,其他全部转移到B盘
         C.push_back(A.back());
         A.pop_back();//这一步是为了把最大的盘子转移到C盘
         dfs(B, A, C,n-1);//这一步是进行递归,B盘变成了A盘,A盘变成了B盘,目的是为了将其他盘全部转移到C盘
    }
};

2.合并两个有序链表(easy)

题目链接:合并两个有序链表
题目描述:将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

在这里插入图片描述

算法思路:

  1. 递归函数的含义:交给你两个链表的头结点,你帮我把它们合并起来,并且返回合并后的头结点;
  2. 函数体:选择两个头结点中较⼩的结点作为最终合并后的头结点,然后将剩下的链表交给递归函数
    去处理;
  3. 递归出⼝:当某⼀个链表为空的时候,返回另外⼀个链表。

注意注意注意:链表的题⼀定要画图,搞清楚指针的操作!

解题思路:

  • 判断某一个参数是否为空,为空则返回 另一个参数

  • 如果两者都不为空,则 比较l1的元素和l2元素的大小

  • 元素***小的节点留下***,并把 该节点的next另一节点 作为下一轮递归函数的参数

  • 下一轮递归函数的 返回值变为该节点的next

  • 返回该节点 (小的那一个)

class Solution {
public:
    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
         if(list1==NULL)
         {
            return list2;
         }

         if(list2==NULL)
          {
            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. 反转链表(easy)

题目链接:反转链表

题⽬描述:

在这里插入图片描述

解题思路:

  • 先通过一层循环找到尾,因为 尾就是新的头节点
  • 找到尾之后,把 head 做为参数,传给递归函数

递归函数:

  • 如果head->next为空,直接返回head

  • 否则,将head->next作为参数进入下一次递归
  • ***下一次递归函数的返回值-***>next=head;
  • head->next=nullptr;
  • 返回head
class Solution {
public:

     ListNode* reverseL(ListNode* head)
     {
       if(head->next==nullptr)
        return head;
     
       else
       { 
        reverseL(head->next)->next=head;
        head->next=nullptr;
        return head;
       }
     }


    ListNode* reverseList(ListNode* head) {
        ListNode* it=head;
        if(head==nullptr)
        return head;
        while(it->next!=nullptr)
        {
            it=it->next;
        }
        reverseL(head);
        return it;
    }
};

4.两两交换链表中的节点(medium)

题目链接:两两交换链表中的节点
题目描述:
在这里插入图片描述

解题思路:

  • 如果(1)head为空,返回nullptr
  • 如果(2)head->next==nullptr,返回head
  • 否则(3)将head->next->next作为参数进行下一次递归
  • 递归的返回值赋值给head->next->next
  • 交换head和head->next
  • 返回原来的head->next
class Solution {
public:
    ListNode* swapPairs(ListNode* head) {


         if(head==nullptr)
        {
            return nullptr;
        }

         else if(head->next==nullptr)
        {
            return head;
        }

         
            head->next->next=swapPairs(head->next->next);
            
            ListNode* it=head->next;
            head->next=head->next->next;
            it->next=head;//交换head和head->next

            return it;
    }
};

结语

解决递归问题时,可以遵循以下经验:

  1. 明确基本情况:确保清楚地定义基本情况,以便在满足条件时能够正确终止递归。

  2. 分解问题:将问题有效地拆分为更小的子问题,确保每次递归调用都朝着基本情况逼近。

  3. 参数管理:仔细选择和管理递归调用中的参数,以便有效传递必要的信息并简化问题。

  4. 结果合并:清晰地规划如何合并子问题的结果,以构建最终解。

  5. 避免重复计算:对于具有重叠子问题的情况(如斐波那契数列),考虑使用记忆化或动态规划来优化性能。

  6. 可视化:在思考递归逻辑时,可以尝试绘制递归树,帮助理解调用过程和结果合并。

  7. 测试和验证:使用边界条件和基本情况测试递归实现,确保其正确性和稳定性。

通过遵循这些经验,可以更有效地解决递归问题,并提高代码的清晰性和可维护性。

好啦,递归问题就讲到这里,下一次讲解的是二叉树的深度搜索,我们下次再见

标签:链表,head,return,递归,C++,next,-------,算法,ListNode
From: https://blog.csdn.net/2301_80374809/article/details/143452697

相关文章

  • 达梦DM-统计用户下每个表的行数和数据量大小
    1,统计用户下每个表的行数和数据量大小–创建一张临时表,用来记录每张表的数据量情况createtabletable_count(ownervarchar(100),table_namevarchar(100),cntint);–执行存储过程统计指定模式每张表数据条数模式名改为要查询的对应的模式即可declarev_ownerVARCHAR2(100)......
  • 强化学习算法——TPG算法(遗传编程GP算法)代码
    tpg算法是一个使用模块涌现和复用机制的遗传编程(GP)算法,该算法在一些强化学习问题上有着不错的表现,本文给出该算法的项目地址。unused_code_chunks.cpp调试代码,实际项目的运行中并没有使用。[]项目所属实验室地址:https://creativealgorithms.ca/tpg算法的项目代......
  • WinAppDriver-PC端自动化
    一、WinAppDriver安装配置1、进入WinAppDriver下载页面(https://github.com/Microsoft/WinAppDriver/releases),下载WinAppDriver安装程序(找到MSI安装应用程序) 2、双击安装即可安装路径下会有一个WinAppDriver.exe程序文件,双击启动即可运行 3、打开电脑开发人员模式 二、......
  • C++模拟真人动态生成鼠标滑动路径
    一.简介鼠标轨迹算法是一种模拟人类鼠标操作的程序,它能够模拟出自然而真实的鼠标移动路径。鼠标轨迹算法的底层实现采用C/C++语言,原因在于C/C++提供了高性能的执行能力和直接访问操作系统底层资源的能力。鼠标轨迹算法具有以下优势:模拟人工轨迹:算法能够模拟出非贝塞尔曲线的......
  • PyTorchStepByStep - Chapter 9: Sequence-to-Sequence
     points,directions=generate_sequences(n=256,seed=13)Andthenlet’svisualizethefirstfivesquares:classEncoder(nn.Module):def__init__(self,n_features,hidden_dim):super().__init__()self.n_features=n_features......
  • 2-petalinux 问题记录-VFS: Cannot open root device "mmcblk0p2" or unknown-block(1
    前言这个问题跟前面记录的问题0和1有点类似吧,也是需要再文件树里面增加一点配置。我手上是有两块zynq,一块是xczu2cg另一块是zynq7010,也就是zynqMP和zynq,在MPSOC里面SD启动需要注意这个SD卡的读写问题。原因SD卡有两种规格,一种大的,标准的SD卡;一种小的,MicroSD卡。如果是大SD卡......
  • Java+Uni-App基于微信小程序的生日礼品管理系统/生日礼物策划系统
    项目介绍科学技术日新月异,人们的生活都发生了翻天覆地的变化,生日福利管理当然也不例外。过去的信息管理都使用传统的方式实行,既花费了时间,又浪费了精力。在信息如此发达的今天,我们可以通过网络这个媒介,快速的查找自己想要的信息,更加全方面的了解自己的网站信息。而......