分治法和递归(Go语言)

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

分治法和递归

分治法字面意思分而治之,就是把一个复杂的问题分成两个或更多的相同或相似的子问题。直到最后子问题可以简单的直接求解,原问题的解及子问题的解的合并。分治法一般使用递归来解决问题

一、递归

递归就是不断的调用函数本身
例如:求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处



>理论上所有的递归方式都可以转化为非递归的方式,只不过使用递归,代码的可读性更高
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值