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的阶乘为例。递归过程分为递和归两个阶段。递的阶段: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 调用 IsOdd ,IsOdd 调回 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、返回值计算
// 主方法:接收用户输入并调用递归方法
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
// 主方法
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缩小问题。第五,递归过程分递和归两个阶段,递是层层向下调用,归是层层向上返回计算结果。
感谢学习
谢谢!
从阶乘到奇偶判断,递归的核心思想你已全部掌握
这节微课就到这里。我们从递归概念、两大要素、直接递归阶乘、间接递归奇偶判断,到完整实训代码,全面拆解了递归的所有重点和难点。希望大家课后多加练习,熟练掌握递归编程思想。