分治法和递归

在计算机科学中,分治法是一种很重要的算法。

字面上的解释是分而治之,就是把一个复杂的问题分成两个或更多的相同或相似的子问题。

直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。

分治法一般使用递归来求问题的解。

一、递归

递归就是不断地调用函数本身。

比如我们求阶乘1 * 2 * 3 * 4 * 5 *...* N

package main
import "fmt" func Rescuvie(n int) int {
if n == 0 {
return 1
} return n * Rescuvie(n-1)
} func main() {
fmt.Println(Rescuvie(5))
}

会反复进入一个函数,它的过程如下:

Rescuvie(5)
{5 * Rescuvie(4)}
{5 * {4 * Rescuvie(3)}}
{5 * {4 * {3 * Rescuvie(2)}}}
{5 * {4 * {3 * {2 * Rescuvie(1)}}}}
{5 * {4 * {3 * {2 * 1}}}}
{5 * {4 * {3 * 2}}}
{5 * {4 * 6}}
{5 * 24}
120

函数不断地调用本身,并且还乘以一个变量:n * Rescuvie(n-1),这是一个递归的过程。

很容易看出, 因为递归式使用了运算符,每次重复的调用都使得运算的链条不断加长,系统不得不使用栈进行数据保存和恢复。

如果每次递归都要对越来越长的链进行运算,那速度极慢,并且可能栈溢出,导致程序奔溃。

所以有另外一种写法,叫尾递归:

package main
import "fmt" func RescuvieTail(n int, a int) int {
if n == 1 {
return a
} return RescuvieTail(n-1, a*n)
} func main() {
fmt.Println(RescuvieTail(5, 1))
}

他的递归过程如下:

RescuvieTail(5, 1)
RescuvieTail(4, 1*5)=RescuvieTail(4, 5)
RescuvieTail(3, 5*4)=RescuvieTail(3, 20)
RescuvieTail(2, 20*3)=RescuvieTail(2, 60)
RescuvieTail(1, 60*2)=RescuvieTail(1, 120)
120

尾部递归是指递归函数在调用自身后直接传回其值,而不对其再加运算,效率将会极大的提高。

如果一个函数中所有递归形式的调用都出现在函数的末尾,我们称这个递归函数是尾递归的。当递归调用是整个函数体中最后执行的语句且它的返回值不属于表达式的一部分时,这个递归调用就是尾递归。尾递归函数的特点是在回归过程中不用做任何操作,这个特性很重要,因为大多数现代的编译器会利用这种特点自动生成优化的代码。-- 来自百度百科。

尾递归函数,部分高级语言编译器会进行优化,减少不必要的堆栈生成,使得程序栈维持固定的层数,不会出现栈溢出的情况。

我们将会举多个例子说明。

二、例子:斐波那契数列

斐波那契数列是指,后一个数是前两个数的和的一种数列。如下:

1 1 2 3 5 8 13 21 ... N-1 N 2N-1

尾递归的求解为:

package main
import "fmt" func F(n int, a1, a2 int) int {
if n == 0 {
return a1
} return F(n-1, a2, a1+a2) } func main() {
fmt.Println(F(1, 1, 1))
fmt.Println(F(2, 1, 1))
fmt.Println(F(3, 1, 1))
fmt.Println(F(4, 1, 1))
fmt.Println(F(5, 1, 1))
}

输出:

1
2
3
5
8

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"

// 二分查找递归解法
func BinarySearch(array []int, target int, l, r int) int {
if l > r {
// 出界了,找不到
return -1
} // 从中间开始找
mid := (l + r) / 2
middleNum := array[mid] if middleNum == target {
return mid // 找到了
} else if middleNum > target {
// 中间的数比目标还大,从左边找
return BinarySearch(array, target, 1, mid-1)
} else {
// 中间的数比目标还小,从右边找
return BinarySearch(array, target, mid+1, r)
} } 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 -1
189 7

可以看到,189这个数字在数列的下标7处,而500这个数找不到。

当然,递归解法都可以转化为非递归,如:

package main

import "fmt"

// 二分查找非递归解法
func BinarySearch2(array []int, target int, l, r int) int {
ltemp := l
rtemp := r for {
if ltemp > rtemp {
// 出界了,找不到
return -1
} // 从中间开始找
mid := (ltemp + rtemp) / 2
middleNum := array[mid] if middleNum == target {
return mid // 找到了
} else if middleNum > target {
// 中间的数比目标还大,从左边找
rtemp = mid - 1
} else {
// 中间的数比目标还小,从右边找
ltemp = mid + 1
}
}
} func main() {
array := []int{1, 5, 9, 15, 81, 89, 123, 189, 333}
target := 500
result := BinarySearch2(array, target, 0, len(array)-1)
fmt.Println(target, result) target = 189
result = BinarySearch2(array, target, 0, len(array)-1)
fmt.Println(target, result)
}

很多计算机问题都可以用递归来简化求解,理论上,所有的递归方式都可以转化为非递归的方式,只不过使用递归,代码的可读性更高。

系列文章入口

我是陈星星,欢迎阅读我亲自写的 数据结构和算法(Golang实现),文章首发于 阅读更友好的GitBook

数据结构和算法(Golang实现)(8.2)基础知识-分治法和递归的更多相关文章

  1. 数据结构和算法(Golang实现)(8.1)基础知识-前言

    基础知识 学习数据结构和算法.我们要知道一些基础的知识. 一.什么是算法 算法(英文algorithm)这个词在中文里面博大精深,表示算账的方法,也可以表示运筹帷幄的计谋等.在计算机科技里,它表示什么 ...

  2. 数据结构和算法(Golang实现)(9)基础知识-算法复杂度及渐进符号

    算法复杂度及渐进符号 一.算法复杂度 首先每个程序运行过程中,都要占用一定的计算机资源,比如内存,磁盘等,这些是空间,计算过程中需要判断,循环执行某些逻辑,周而反复,这些是时间. 那么一个算法有多好, ...

  3. 数据结构和算法(Golang实现)(10)基础知识-算法复杂度主方法

    算法复杂度主方法 有时候,我们要评估一个算法的复杂度,但是算法被分散为几个递归的子问题,这样评估起来很难,有一个数学公式可以很快地评估出来. 一.复杂度主方法 主方法,也可以叫主定理.对于那些用分治法 ...

  4. 数据结构和算法(Golang实现)(25)排序算法-快速排序

    快速排序 快速排序是一种分治策略的排序算法,是由英国计算机科学家Tony Hoare发明的, 该算法被发布在1961年的Communications of the ACM 国际计算机学会月刊. 注:A ...

  5. 数据结构和算法(Golang实现)(1)简单入门Golang-前言

    数据结构和算法在计算机科学里,有非常重要的地位.此系列文章尝试使用 Golang 编程语言来实现各种数据结构和算法,并且适当进行算法分析. 我们会先简单学习一下Golang,然后进入计算机程序世界的第 ...

  6. 数据结构和算法(Golang实现)(2)简单入门Golang-包、变量和函数

    包.变量和函数 一.举个例子 现在我们来建立一个完整的程序main.go: // Golang程序入口的包名必须为 main package main // import "golang&q ...

  7. 数据结构和算法(Golang实现)(3)简单入门Golang-流程控制语句

    流程控制语句 计算机编程语言中,流程控制语句很重要,可以让机器知道什么时候做什么事,做几次.主要有条件和循环语句. Golang只有一种循环:for,只有一种判断:if,还有一种特殊的switch条件 ...

  8. 数据结构和算法(Golang实现)(4)简单入门Golang-结构体和方法

    结构体和方法 一.值,指针和引用 我们现在有一段程序: package main import "fmt" func main() { // a,b 是一个值 a := 5 b : ...

  9. 数据结构和算法(Golang实现)(5)简单入门Golang-接口

    接口 在Golang世界中,有一种叫interface的东西,很是神奇. 一.数据类型 interface{} 如果你事前并不知道变量是哪种数据类型,不知道它是整数还是字符串,但是你还是想要使用它. ...

随机推荐

  1. nuxt创建项目的步骤

    nuxt创建项目的步骤 1.基本步骤 // 创建package.json依赖管理文件 npm init -y // 在package.json文件中添加运行nuxt的命令,之后npm run dev启 ...

  2. python迭代器、装饰器和生成器

    装饰器 1.装饰器的作用 1. 装饰器作用:本质是函数(装饰其他函数)就是为其他函数添加其他功能 2. 装饰器必须准寻得原则: 1)不能修改被装饰函数的源代码 2)不能修改被装饰函数的调用方式 3.实 ...

  3. 040.集群网络-CNI网络模型

    一 CNM网络模型 1.1 网络模型 生产环境中,跨主机容器间的网络互通已经成为基本要求,更高的要求包括容器固定IP地址.一个容器多个IP地址.多个子网隔离.ACL控制策略.与SDN集成等.目前主流的 ...

  4. Web过滤器和监听器

    1.过滤器 1.1什么是过滤器 Filter也称之为过滤器,它是Servlet技术中最激动人心的技术,WEB开发人员通过Filter技术,对web服务器管理的所有web资源:例如Jsp, Servle ...

  5. 目标检测 | 经典算法 Cascade R-CNN: Delving into High Quality Object Detection

    作者从detector的overfitting at training/quality mismatch at inference问题入手,提出了基于multi-stage的Cascade R-CNN ...

  6. 免费IP归属地查询接口汇总

    目前做一个项目,需要判断是国内还是国外的IP,具体要求为接口稳定,速度快,免费,不异常,所以我整理了优质的接口供大家筛选. IP归属地查询API 一,淘宝API接口 http://ip.taobao. ...

  7. 文本编辑器之kindeditor

    摘要:最近在自己学习搭建网站的时候,突然要搭建网站的时候发现了一个好东西,那就是kindeditor这个文本编辑器,这个编辑器简单好用,而且很小,并且是开源的. 文本编辑器介绍 KindEditor ...

  8. 01背包模板题 hdu2602 Bonecollector

    由于数组的滚动过程中当前值(i,j)的更新需要用到上一层的(i-1,j-wi)的值,所以在更新当前的j之前不能更新上一层的j之前的值,故01背包是从后向前更新的(重量取值是从大到小的). 代码如下: ...

  9. P5020 货币系统 题解

    原题链接 简要题意: 求一个长度最小的货币系统与给出的货币系统等价.求这个货币系统的长度.等价的定义详见题目,不再赘述. 本文可能用到一些集合论,请放心食用. 算法一 \(n=2\) 时,只需判断两个 ...

  10. JMeter入门介绍

    目录 概述 下载&安装 实战JMetetr 测试计划简述 准备测试计划 编写测试计划 录制测试脚本 执行性能测试 单机测试 分布式测试 分析测试报告 APDEX 响应时间和吞吐量统计 测试结果 ...