递归封面背景
中职 C# 程序设计 · 专项微课
6-8 分钟

拆解递归
核心重难点

自己调自己 · 基线条件终止 · 递归条件缩小问题
直接递归与间接递归,像套娃一样的编程思想

同学们大家好!递归是一种非常重要的编程思想。将要处理的问题划分为一个或多个子问题,而处理子问题的方法与处理原问题的方法是一样的,这样的处理方法称为递归。在C#中,递归分为直接递归和间接递归两种。今天这节微课,我们就逐层拆解递归的所有重点和难点。

Contents · 本课内容

4 大模块 · 全流程覆盖

从原理到实训,让你独立看懂、理解通透、会写会改代码

01

递归概念 + 直接递归(阶乘)

自己调自己、基线条件、递归条件、阶乘调用栈

02

间接递归(奇偶判断)

IsEven/IsOdd 互相"踢皮球"、间接递归调用链

03

实训案例:阶乘 + 奇偶判断

【实训5-1】计算阶乘、间接递归判断奇偶性

04

VS2010 示范操作 + 完整代码

新建项目到运行调试,手把手实操

本节课我们将逐一攻克4大模块:递归概念与直接递归、间接递归、实训案例、以及VS2010示范操作。循序渐进,零基础也能轻松学懂递归。

重点突破 · 01

递归概念
与两大要素

将问题划分为子问题,处理子问题的方法与原问题相同——这就是递归

首先我们掌握本节课第一个核心重点:递归的概念。递归就是要将处理的问题划分为一个或多个子问题,而处理子问题的方法与处理原问题的方法是一样的。在C#中,递归分为两种类型:直接递归和间接递归。

核心概念 · 直接递归 vs 间接递归

两种递归类型

递归 = 将问题划分为子问题 + 处理方法与原问题相同

直接递归

🔄 自己调自己

  • 在方法中直接调用方法本身
  • 例如 Factorial(n) 调用 Factorial(n-1)
  • 一个问题 → 化为更小的同类问题
  • 直到触发基线条件停止递归
static long Factorial(int n)
{
  if (n == 0) return 1;
  return n * Factorial(n - 1);
}
间接递归

🔁 互相"踢皮球"

  • 方法A调用方法B,方法B反向调用方法A
  • 例如 IsEven 调用 IsOdd,IsOdd 调用 IsEven
  • 两个方法间接地形成递归
  • 直到触发基线条件停止递归
static bool IsEven(int n)
{
  if (n == 0) return true;
  return IsOdd(n - 1);
}
递归分为两种类型。直接递归是在方法中调用方法本身,例如阶乘函数Factorial调用Factorial(n-1)。间接递归是间接地调用一个方法,例如第一个方法IsEven调用了第二个方法IsOdd,IsOdd又反向调用IsEven,形成间接递归。两种递归都必须有基线条件来终止递归,否则会无限循环。

难点突破 · 01

递归两大
核心要素

基线条件(停止) + 递归条件(缩小问题)——缺一不可

理解了递归的概念,我们攻克本节课的难点:递归的两大核心要素。任何递归方法都必须具备两个条件:基线条件和递归条件。基线条件让递归停止,递归条件将问题缩小。两者缺一不可,缺少基线条件会导致无限递归。

难点突破 · 基线条件 + 递归条件

递归 = 基线条件 + 递归条件

缺少基线条件 → 无限递归 → 栈溢出;缺少递归条件 → 不会缩小问题

🛑

基线条件(Base Case)

递归的"刹车"——满足条件时停止递归,直接返回结果

// 阶乘的基线条件 if (n == 0) return 1; // 奇偶的基线条件 if (n == 0) return true; // 偶数 if (n == 0) return false; // 奇数

关键:必须有明确的终止条件,否则递归永不停止,导致栈溢出(StackOverflowException)

🔄

递归条件(Recursive Case)

递归的"引擎"——将问题缩小为更小的同类问题

// 阶乘的递归条件 return n * Factorial(n - 1); // 奇偶的递归条件 return IsOdd(n - 1); // IsEven内 return IsEven(n - 1); // IsOdd内

关键:每次递归调用,问题规模必须缩小(n→n-1),逐步逼近基线条件

核心口诀:递归 = 基线条件(停止) + 递归条件(缩小)。每次调用必须更接近基线条件,否则就是死循环!

递归必须具备两大要素。基线条件是递归的刹车,满足条件时停止递归直接返回结果。比如阶乘的基线条件是n等于0时返回1,奇偶判断的基线条件是n等于0时返回true或false。递归条件是递归的引擎,将问题缩小为更小的同类问题。比如阶乘中n乘以Factorial(n-1),奇偶中IsEven调用IsOdd(n-1)。每次递归调用,问题规模必须缩小,逐步逼近基线条件。如果缺少基线条件会导致无限递归和栈溢出。

直接递归 · 阶乘调用栈可视化

以 5! = 120 为例

计算 5! = ?
计算 3! = ?
⬇ 递(向下调用)
⬆ 归(向上返回)
蓝色=递推调用 · 绿色=基线条件 · 橙色=回归计算
 
当前阶段:- 当前步骤:- 计算结果:-
我们以5的阶乘为例。递归过程分为递和归两个阶段。递的阶段:Factorial(5)调用Factorial(4),Factorial(4)调用Factorial(3),以此类推直到Factorial(0)。当n=0时触发基线条件返回1。归的阶段:Factorial(1)返回1*1=1,Factorial(2)返回2*1=2,Factorial(3)返回3*2=6,Factorial(4)返回4*6=24,Factorial(5)返回5*24=120。整个过程像套娃一样,一层层打开再一层层合上。

难点突破 · 02

间接递归
"踢皮球"

IsEven → IsOdd → IsEven → IsOdd... 两个方法互相调用

接下来我们攻克间接递归。间接递归是指方法A调用方法B,方法B又反向调用方法A,形成间接的递归调用。我们以判断奇偶性为例:IsEven和IsOdd两个方法互相"踢皮球",直到n减到0触发基线条件。

间接递归 · IsEven / IsOdd 调用链

以 IsEven(5) 为例

IsEven(5) 奇数
IsEven(4) 偶数
调用 IsEven(5)
IsEven 调用 IsOddIsOdd 调回 IsEven,直到 n=0
 
当前调用:- 当前步骤:- 判断结果:-
我们以IsEven(5)为例。IsEven(5)发现n不等于0,调用IsOdd(4)。IsOdd(4)发现n不等于0,调用IsEven(3)。IsEven(3)调用IsOdd(2)。IsOdd(2)调用IsEven(1)。IsEven(1)调用IsOdd(0)。IsOdd(0)触发基线条件返回false。最终结果:5是奇数,IsEven(5)返回false,IsOdd(5)返回true。两个方法像踢皮球一样,每调用一次n减1,直到n=0。

代码实战 · 直接递归 · 阶乘

Factorial 方法逐行分析

重点关注:基线条件 n==0、递归调用 n-1、返回值计算

Program.cs — 直接递归 · 阶乘
// 主方法:接收用户输入并调用递归方法 static void Main() { Console.WriteLine("请输入一个正整数:"); int n = Convert.ToInt16(Console.ReadLine()); Console.WriteLine("{0}! ={1}", n, Factorial(n)); Console.ReadKey(); } // 自己调自己 → 直接递归 static long Factorial(int n) { if (n == 0) return 1; // 基线条件:0! = 1 return n * Factorial(n - 1); // 递归条件:n! = n × (n-1)! }
🛑

基线条件

if (n == 0) return 1

n等于0时停止递归,返回1。0的阶乘定义为1,这是递归的终止点。

🔄

递归条件

return n * Factorial(n-1)

n的阶乘等于n乘以(n-1)的阶乘。每次n减1,逐步逼近基线条件。

📦

返回值类型

static long Factorial

返回值用long而非int,因为阶乘增长很快,int容易溢出。

这是直接递归的阶乘代码。主方法接收用户输入的正整数n,调用Factorial方法。Factorial方法只有两行核心代码:基线条件if(n==0)return 1,当n等于0时返回1停止递归;递归条件return n*Factorial(n-1),n的阶乘等于n乘以n-1的阶乘。返回值用long类型,因为阶乘增长很快,int容易溢出。比如5!=120,但13!就超过了int的范围。

代码实战 · 间接递归 · 奇偶判断

IsEven / IsOdd 逐行分析

两个方法互相调用,每调用一次 n 减 1,直到 n==0

Program.cs — 间接递归 · 奇偶判断
// 主方法 static void Main(string[] args) { Console.WriteLine("请输入一个正整数:"); int x = Convert.ToInt16(Console.ReadLine()); Console.WriteLine("{0} 是偶数?{1}", x, IsEven(x)); Console.WriteLine("{0} 是奇数?{1}", x, IsOdd(x)); Console.ReadKey(); } // 判断偶数 → 调用 IsOdd static bool IsEven(int n) { if (n == 0) return true; // 0 是偶数 return IsOdd(n - 1); // 交给 IsOdd 判断 } // 判断奇数 → 调用 IsEven static bool IsOdd(int n) { if (n == 0) return false; // 0 不是奇数 return IsEven(n - 1); // 交回 IsEven 判断 }

💡 理解关键:IsEven(n) 把问题"踢"给 IsOdd(n-1),IsOdd(n-1) 又"踢"回 IsEven(n-2)... 每踢一次 n 减 1,最终 n=0 时:IsEven(0)=true(偶数),IsOdd(0)=false(奇数)

这是间接递归的奇偶判断代码。IsEven方法判断是否为偶数:基线条件n==0返回true,否则调用IsOdd(n-1)。IsOdd方法判断是否为奇数:基线条件n==0返回false,否则调用IsEven(n-1)。两个方法互相调用,每调用一次n减1,直到n等于0触发基线条件。理解关键:IsEven把问题踢给IsOdd,IsOdd又踢回IsEven,每踢一次n减1,最终n=0时IsEven返回true(偶数),IsOdd返回false(奇数)。

实训案例 · 【实训5-1】

递归
实训实战

直接递归:计算阶乘 · 间接递归:判断奇偶性

现在进入实训环节。实训内容包括两个案例:直接递归计算阶乘,以及间接递归判断奇偶性。

实训5-1 · 算法步骤分析

两个实训案例对比

A

直接递归:计算阶乘 5!

Factorial(5) → 5×Factorial(4) → 4×Factorial(3) → 3×Factorial(2) → 2×Factorial(1) → 1×Factorial(0) → 基线条件返回1 → 1×1=1 → 2×1=2 → 3×2=6 → 4×6=24 → 5×24=120

B

间接递归:判断 5 的奇偶性

IsEven(5) → IsOdd(4) → IsEven(3) → IsOdd(2) → IsEven(1) → IsOdd(0) → 基线条件返回false → 最终:IsEven(5)=false(奇数),IsOdd(5)=true(奇数)

共同点:两种递归都需要基线条件来终止。直接递归中 Factorial(0)=1 是基线;间接递归中 IsEven(0)=true 和 IsOdd(0)=false 是基线。区别:直接递归自己调自己,间接递归两个方法互相调。

实训分析两个案例。案例A直接递归计算5的阶乘:Factorial(5)依次调用到Factorial(0)返回1,然后依次计算1、2、6、24、120。案例B间接递归判断5的奇偶性:IsEven(5)调用IsOdd(4),IsOdd(4)调用IsEven(3),以此类推直到IsOdd(0)返回false,最终IsEven(5)返回false即5是奇数。两种递归都需要基线条件终止,区别在于直接递归自己调自己,间接递归两个方法互相调。

实训代码 · 完整实现

递归实训完整代码 + 运行结果

Program.cs — 递归实训(阶乘 + 奇偶)
// ====== 实训5-1:直接递归计算阶乘 ====== static void Main() { Console.WriteLine("请输入一个正整数:"); int n = Convert.ToInt16(Console.ReadLine()); Console.WriteLine("{0}! ={1}", n, Factorial(n)); Console.ReadKey(); } // 自己调自己 → 直接递归 static long Factorial(int n) { if (n == 0) return 1; return n * Factorial(n - 1); } // ====== 间接递归判断奇偶性 ====== static void Main(string[] args) { Console.WriteLine("请输入一个正整数:"); int x = Convert.ToInt16(Console.ReadLine()); Console.WriteLine("{0} 是偶数?{1}", x, IsEven(x)); Console.WriteLine("{0} 是奇数?{1}", x, IsOdd(x)); Console.ReadKey(); } static bool IsEven(int n) { if (n == 0) return true; return IsOdd(n - 1); } static bool IsOdd(int n) { if (n == 0) return false; return IsEven(n - 1); }
程序运行结果
> 请输入一个正整数: 5 5! =120 ──────────────────────── > 请输入一个正整数: 5 5 是偶数?False 5 是奇数?True
这是完整的实训代码。新建项目选择控制台应用程序,在Main方法中输入代码。第一个实训是直接递归计算阶乘:输入5,输出5!=120。第二个实训是间接递归判断奇偶性:输入5,输出5是偶数False、5是奇数True。运行时在Visual Studio 2010中按F5启动调试。注意Convert.ToInt16用于将输入字符串转为整数,Factorial方法返回long类型。

Summary · 核心口诀总结

五大要点,一次吃透递归

从原理到实训,截图保存随时复习

1

递归定义

将问题划分为子问题,处理子问题的方法与原问题相同

2

两种类型

直接递归:自己调自己(Factorial);间接递归:互相调(IsEven/IsOdd)

3

基线条件

递归的"刹车":n==0时停止并返回。缺少则无限递归→栈溢出

4

递归条件

递归的"引擎":每次n-1缩小问题,逐步逼近基线条件

5

递归过程

分"递"和"归"两个阶段:递=层层向下调用直到基线条件;归=层层向上返回计算结果。5! = 5×4×3×2×1 = 120

总结五大要点:第一,递归定义是将问题划分为子问题,处理方法与原问题相同。第二,两种类型:直接递归自己调自己,间接递归两个方法互相调。第三,基线条件是递归的刹车,n等于0时停止,缺少会导致栈溢出。第四,递归条件是递归的引擎,每次n减1缩小问题。第五,递归过程分递和归两个阶段,递是层层向下调用,归是层层向上返回计算结果。

感谢学习

谢谢!

从阶乘到奇偶判断,递归的核心思想你已全部掌握

这节微课就到这里。我们从递归概念、两大要素、直接递归阶乘、间接递归奇偶判断,到完整实训代码,全面拆解了递归的所有重点和难点。希望大家课后多加练习,熟练掌握递归编程思想。