递归、尾递归、递归优化

440 阅读2分钟

问题引入

斐波那且数列 : 1 1 2 3 5 8 13 21 ... (从第三项开始的数等于前面两个数之和)

代码实现

func fibo(n int) int {
	if n <= 2 {
		return 1
	} else {
		return fibo(n-1) + fibo(n-2)
	}
}

分析

假如求fibo(10), 则会要先求fibo(9)和fibo(8),然后求fibo(9)则先要求fibo(8)和fibo(7),
同理,求fibo(8)则先要求fibo(7)和fibo(6)... 依此类推(充斥着大量的重复计算)。
因此,求第n项的值,时间复杂度为 2^n。

优化方向

优化方向一:由于充斥着大量的重复计算,如果能加入缓存把已经计算过的存起来,则可以大大减少计算量。以空间换取时间,大大减少执行时间

var arr = make([]int, 50) // 全局变量,切片 用于加入缓存
func fibo1(n int) int {
	if n <= 2 {
		return 1
	}
	if arr[n] > 0 { // 先检查值是否已经计算过,如果计算过,则直接返回
		return arr[n]
	} else {
		res := fibo1(n-1) + fibo1(n-2) 
		arr = append(arr, res) // 如果没有计算过,则加入数组缓存
		return res
	}
}

优化方向二:任何递归可以解决的事情,都可以使用循环解决,将时间复杂度由2^n将为O(n),效率大大提升

func fibo3(n int) int {
	var c int // 相当于计算总和
	var a int = 1 // 相当于fibo1(n-1)
	var b int = 1 // 相当于fibo1(n-2)
	if n <= 2 {
		return 1
	} else {
		for i := 3; i <= n; i++ { // 从第三项开始
			c = a + b    // 相当于 fibo1(n-1) + fibo1(n-2) 
			a = b 
			b = c
		}
		return c
	}
}

优化方向三:尾递归方式解决(尾递归就是将上一次的计算结果传递到下一次计算当中)

/**
* pre 上一次计算结果
* res 当前结果
* n 项
*/
func fibo2(pre int, res int, n int) int {
	if n <= 2 {
		return res
	} else {
		return fibo2(res, pre+res, n-1)
	}
}

另外一个例子,求 n!

递归方式 尾递归

func mul(n int, res int) int {
	if n <= 1 {
		return res
	} else {
		res = n * res
		return mul(n-1, res)
	}
}

循环方式

//计算阶乘 n! 非递归方式
func noRecMul(n int) int {
	if n <= 1 {
		return 1
	} else {
		res := 1
		for i := n; i > 0; i-- {
			res = res * i
		}
		return res
	}
}

总结

虽然递归可以让代码看起来更加通俗易懂,但是其时间复杂度是存在极大的优化空间的,因此如果在项目中遇到
类似问题,可以从以上几个方向去优化