分治法和递归
分治法字面意思分而治之,就是把一个复杂的问题分成两个或更多的相同或相似的子问题。直到最后子问题可以简单的直接求解,原问题的解及子问题的解的合并。分治法一般使用递归来解决问题
一、递归
递归就是不断的调用函数本身
例如:求N的阶乘
package main
import "fmt"
// 阶乘
func Factorial(n int) int {
if n == 0 {
return 1
}
return n * Factorial(n-1)
}
func main() {
fmt.Println(Factorial(5))
}
在这段代码中,函数会进行下面的步骤
Factorial(5)
{5 * Factorial(4)}
{5 * {4 * Factorial(3)}}
{5 * {4 * {3 * Factorial(2)}}}
{5 * {4 * {3 * {2 * Factorial(1)}}}}
{5 * {4 * {3 * {2 * 1}}}}
{5 * {4 * {3 * 2}}}
{5 * {4 * 6}}
{5 * 24}
120
函数不断调用本身,本且还乘以一个变量:n * Factorial(n-1),这是一个递归的过程
于此同时,每一次重复的调用都使得运算符的链长不断加长,系统要使用栈来进行数据保存和恢复,如果每次递归都要对越来越长的链进行运算,那么速度极慢,还有可能导致栈溢出,导致程序崩溃
另一种写法:尾递归
package main
import "fmt"
// 阶乘
func FactorialTail(n int, a int) int {
if n == 0 {
return a
}
return FactorialTail(n-1, a*n)
}
func main() {
fmt.Println(FactorialTail(5, 1))
}
递归过程如下:
FactorialTail(5, 1)
FactorialTail(4, 1*5)=FactorialTail(4, 5)
FactorialTail(3, 5*4)=FactorialTail(3, 20)
FactorialTail(2, 20*3)=FactorialTail(2, 60)
FactorialTail(1, 60*2)=FactorialTail(1, 120)
120
尾部递归是指调用递归函数在调用自身后直接传回其值,而不对其再次加减运算,效率会极大提高。
如果一个函数中所有递归形式的调用都出现在函数的末尾,我们成这个递归函数是尾递归。当递归用时整个函数体中最后执行的语句且返回值不属于表达式的一部分时,这个递归调用就是尾递归。尾递归函数的特点就是在回归过程中不会做任何操作,这特性很重要,因为大多数现代编译器会利用这种特点自动生成优化的代码。 --百度百科
尾递归函数,部分高级语言编译器会进行优化,减少不必要的堆栈生成,使得程序栈维持固定的层数,不会出现栈溢出的情况
二、斐波那契额数列
斐波那契额数列是指,后一个数时前面两个数的和的一种数列,例如:
1 1 2 3 5 8 13 21 ... N-1 N 2N-1
尾递归求解:
package main
import "fmt"
// 斐波那契数列
func F(n, a1, a2 int) int {
if n == 0 {
return a1
}
return F(n-1, a2, a1+a2)
}
func main() {
fmt.Println("斐波那契数列", F(5, 1, 1))
}
当n=5的递归如下:
F(5,1,1)
F(4,1,1+1)=F(4,1,2)
F(3,2,1+2)=F(3,2,3)
F(2,3,2+3)=F(2,3,5)
F(1,5,3+5)=F(1,5,8)
F(0,8,5+8)=F(0,8,13)
8
三、二分查找
在一个已经排好序的数列,找出某个数,如:
1 5 9 15 81 89 123 189 333
从上面的数字找出189.
二分查找的思路时,
- 先拿排好序的数列的中位数与189对比,如果刚好匹配目标,结束。
- 如果中位数比目标数字大,因为已经排好序的原因,所以从中位数的左边找
- 如果中位数比目标数字小,因为已经排好序的原因,所以从中位数的右边找
这种分而治之,一分为二的查找叫二分查找算法
递归解法:
package main
import "fmt"
// BinarySearch 二分查找递归法
func BinarySearch(array []int, target int, left, right int) int {
if left > right {
// 及出界了,不能继续寻找了
return -1
}
// 从中间开始寻找
mid := (left + right) / 2
middleNum := array[mid]
if middleNum == target {
return mid // 找到了
} else if middleNum > target {
// 中间数比目标数字大,从左边寻找
return BinarySearch(array, target, 0, mid-1)
} else if middleNum < target {
// 中间数字比目标数字小,从右边寻找
return BinarySearch(array, target, mid+1, right)
}
return 0
}
func main() {
array := []int{1, 5, 9, 15, 81, 89, 123, 189, 333}
target := 500
result := BinarySearch(array, target, 0, len(array)-1)
fmt.Println(target, result)
target = 189
result = BinarySearch(array, target, 0, len(array)-1)
fmt.Println(target, result)
}
输出结果:

500没有找到,而189这个数字在下标7处
理论上所有的递归方式都可以转化为非递归的方式,只不过使用递归,代码的可读性更高
arget, 0, len(array)-1)
fmt.Println(target, result)
}
输出结果:
[外链图片转存中...(img-h9Ivw6D9-1643763990840)]
500没有找到,而189这个数字在下标7处
>理论上所有的递归方式都可以转化为非递归的方式,只不过使用递归,代码的可读性更高

本文探讨了分治法的基本概念,如何通过递归实现阶乘、斐波那契数列和二分查找,并揭示了尾递归优化的重要性。通过实例解析,展示了如何将递归转换为非递归,提升效率。
134

被折叠的 条评论
为什么被折叠?



