理解循环和递归的区别
循环
循环比较好理解,就是一条通道不停开门,不停前行,直到开不了门,就跳出这个通道。
如果是死循环,则相当于此通道永无止尽,
- 可能是一个环形通道,
- 可能是一条串行无限的通道。
递归
递归则相当于一条通道不停开门,不停前行,直到开不了门,则从 原路返回 。
所以“递归”从字面上看就非常准确地表达了其含义。
- 递:依次、一层层
- 归:回来,回头执行
实例分析递归的实现过程
一、实例一
1、 递归函数及输出结果
现在我们写一个递归
// 主程序
func main() {
digui(3)
}
//递归函数
func digui(n int) {
if n < 1 {
fmt.Println("结束", 99)
return
}
n--
fmt.Println("进", n)
// 递归调用
digui(n)
n += 1000
fmt.Println("出", n)
}
// 输出
/*
进 2
进 1
进 0
结束 99
出 1000
出 1001
出 1002
*/
2、 解析执行过程
那么他内部是怎么执行的呢?现在我很傻瓜的把函数一层层嵌入进去解析一下~~+
// 主程序
func main() {
digui(3)
}
//传一个参数n=3的拷贝进入digui()函数
func digui(n int) {
//此时n=3,不符合本条件
if n < 1 {
fmt.Println("结束", 99)
return
}
// 执行n--,此时n=2
n--
// 打印第一行:进 2
fmt.Println("进", n)
//digui(n)
// 这里的递归相当于执行了一个闭包函数
// 传一个参数n=2的拷贝进入函数
func(n int) {
//此时n=2,不符合本条件
if n < 1 {
fmt.Println("结束", 99)
return
}
// 执行n--,此时n=1
n--
// 打印第二行:进 1
fmt.Println("进", n)
//digui(n)
// 这里的递归相当于执行了一个闭包函数
// 传一个参数n=1的拷贝进入函数
func(n int) {
//此时n=1,不符合本条件
if n < 1 {
fmt.Println("结束", 99)
return
}
// 执行n--,此时n=0
n--
// 打印第三行:进 0
fmt.Println("进", n)
//digui(n)
// 这里的递归相当于执行了一个闭包函数
// 传一个参数n=0的拷贝进入函数
func(n int) {
// 此时n<1,符合条件
if n < 1 {
// 打印第四行:结束 99
fmt.Println("结束", 99)
//结束本函数,后面的都不执行了
return
}
//因为不执行了,所以后面都注释了
/*
n--
fmt.Println("进", n)
digui(n)
n += 1000
fmt.Println("出", n)
*/
}(n)
// 这一层里的n=0,n+=1000的值为10000
n += 1000
// 第五行打印:出 1000
fmt.Println("出", n)
}(n)
// 这一层里的n=1,n+=1000的值为10001
n += 1000
// 第六行打印:出 1001
fmt.Println("出", n)
}(n)
// 这一层里的n=2,n+=1000的值为10002
n += 1000
// 第七行打印:出 1002
fmt.Println("出", n)
}
注意:
- 本例中函数传参都是值传递,即拷贝,这样外层函数的
n=2
传入内层函数后执行n--
,得到n=1
,但该内层的n
与外层的n
并无关系,因此,在内层函数结束完成后再去执行外层的n+=1000
,这里n的值为1002
。- 解析的例子中可以看出,每层函数结束后都还需要执行一次
n+=1000
和fmt.Println("出",n)
,这也就是所谓的“回头路”了,其本质仍旧是串联执行,一层函数结束后继续执行下去直到本层结束再继续执行~~并没有真的“走回头路”;- 如果把递归的调用放在函数末尾则叫“尾递归”,实行尾递归时,一层结束后没有任务需要执行就跳到下一层,于是函数就“快速”地一层层结束了,让人感觉没有走回头路,所以尾递归用for循环也能实现。
二、实例二
假设有一题:6层台阶,可以一次走两层或者一次走一层,那么有几种走法?
这里就可以用到递归,本题我们只研究递归的实现过程,不研究算法。
1、 递归函数及输出结果
单看这个递归不好理解,递归是一路走下去,直到没路再原路返回,那这个例子中返回值是两个返回值相加,怎么就能一头越变越小,另一头又越来越大呢?而且返回值是两个函数返回值的和,那这两个函数又会分别分出两个函数去生成返回值,那这样分叉越来越多,岂不是无限循环了?
func main() {
fmt.Println(f(6))
}
func f(n int) int {
if n == 1 {
return 1
}
if n == 2 {
return 2
}
return f(n-2) + f(n-1)
}
// 输出:13
2、 解析执行过程
带着一脸的疑惑,我们来看一看具体的执行流程。
假定每一层都有a
和b
两个变量分别接收两个函数的返回值,然后再将a+b
之和返回给当前函数。
下面的解析过程已经去掉了不符合条件的语句,否则太长了。
func main() {
fmt.Println(f(6))
}
// n = 6
func f(n int) int {
// 输入6-2=4
a := func(n int) int {
// 输入4-2=2
a := func(n int) int {
return 2
}(n - 2) // 返回2
// 输入4-1=3
b := func(n int) int {
// 输入3-2=1
a := func(n int) int {
return 1
}(n - 2) // 返回1
// 输入3-1=2
b := func(n int) int {
return 2
}(n - 1) // 返回2
// 返回1+2=3
return a + b
}(n - 1) // 返回3
// 返回2+3=5
return a + b
}(n - 2) // 返回5
// 输入6-1=5
b := func(n int) int {
// 输入5-2=3
a := func(n int) int {
// 输入3-2=1
a := func(n int) int {
return 1
}(n - 2) //返回1
// 输入3-1=2
b := func(n int) int {
return 2
}(n - 1) //返回2
//返回1+2=3
return a + b
}(n - 2) // 返回3
// 输入5-1=4
b := func(n int) int {
// 输入4-2=2
a := func(n int) int {
return 2
}(n - 2) // 返回2
// 输入4-1=3
b := func(n int) int {
// 输入3-2=1
a := func(n int) int {
return 1
}(n - 2) // 返回1
// 输入3-1=2
b := func(n int) int {
return 2
}(n - 1) // 返回2
// 返回1+2=3
return a + b
}(n - 1) // 返回3
// 返回2+3=5
return a + b
}(n - 1) // 返回5
// 返回3+5=8
return a + b
}(n - 1) // 返回8
// 返回5+8=13
return a + b
} // 返回13
看代码的执行过程,我们能看出:
n-2
或n-1
让n
不断变小,n
传递至下一层后,在符合n==1
或n==2
的条件之前,n
一直在不断变小;- 当
n
遇到条件n==1
或n==2
时,它(n
)终于撞南墙return
。return
的意思就是结束本层,进入下一层。- 到达上一层后先执行
a+b
,两者之和return
回上一层,并执行上一层的a+b
…- 因此
n
在不断减小,a+b
不断变大返回。- 与上个例子一样,将递归函数作为标准,该函数之前的行为依次执行,直至遇到返回条件后,再依次执行递归函数之后的程序。
- 从过程中可以总结本题:进入下一层的目的是不断创建分支直至条件满足返回,返回上一层的目的是将分支中的值进行加法计算再供上一层使用。
按照上面的总结,我们怎么让自己一眼看出递归的执行流程呢?
以本例中return f(n-2)+f(n-1)
作为标准;
if
:两句条件语句n==1
或n==2
在调用递归函数之前,因此先执行;n-1
n-2
:n-1
或n-2
作为参数传入f()
,因此这两参数是第二位执行,此后函数在不断执行n-1
或n-2
;if
return
:直到n
满足了条件n==1
或n==2
后返回1
或者2
+
return
:此后不断返回外层,再将f(n-2)+f(n-1)
(即a+b
)之和返回,直至程序结束。