分治法和递归

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

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

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

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

一、递归

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

比如我们求阶乘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. mysql锁机制和事务隔离

    mysql事务 1.InnoDB事务原理 事务(Transaction)是数据库区别于文件系统的重要特性之一,事务会把数据库从一种一致性状态转换为另一种一致性状态. 在数据库提交时,可以确保要么所有修 ...

  2. Mol Cell Proteomics. | 用于鉴定新型融合转录本及其在癌细胞中的潜在翻译产物的多功能蛋白质组基因组学工具FusionPro

    期刊:Molecular & Cellular Proteomics 发表时间:June 17, 2019 DOI:10.1074/mcp.RA119.001456 分享人:任哲 内容与观点: ...

  3. 浏览器与DNS解析过程

    浏览器解析 1.地址栏输入地址后,浏览器检查自身DNS缓存 地址栏输入chrome://net-internals/#dns 查看. 2.浏览器缓存中未找到,那么Chrome会搜索操作系统自身的DNS ...

  4. 3.Scikit-Learn实现完整的机器学习项目

    1       完整的机器学习项目 完成项目的步骤: (1)    项目概述 (2)    获取数据 (3)    发现并可视化数据,发现规律. (4)    为机器学习算法准备数据. (5)    ...

  5. OpenCV-Python 读取显示视频 | 六

    目标 学习读取视频,显示视频和保存视频. 学习从相机捕捉并显示它. 你将学习以下功能:cv.VideoCapture(),cv.VideoWriter() 从相机中读取视频 通常情况下,我们必须用摄像 ...

  6. Java中的集合类、Lambda、鲁棒性简述

    集合类 在java.util包中提供了一些集合类,常用的有List.Set和Map类,其中List类和Set类继承了Collection接口.这些集合类又称为容器,长度是可变的,数组用来存放基本数据类 ...

  7. jdk安装和配置教程

    目录 jdk的下载 jdk的安装 配置环境变量 验证是否配置成功] 一些常见的错误(待更新) 一.首先是jdk的下载 链接:https://pan.baidu.com/s/1ojQDuCwiGSA7A ...

  8. flask前后端输出html页面(数组遍历)

    通过flask,输出页面 后端代码文件:app.py 前端html文件:output.html 1.打开(app.py) 导入相关模块: 2.定义方法:(app.py) 3.写入与后端定义好的参数:( ...

  9. Java 数组 字符 函数

    一. 1. package Hello; import java.util.Scanner; public class hello_test { public static void main(Str ...

  10. TCP漫谈之keepalive和time_wait

    TCP是一个有状态通讯协议,所谓的有状态是指通信过程中通信的双方各自维护连接的状态. 一.TCP keepalive 先简单回顾一下TCP连接建立和断开的整个过程.(这里主要考虑主流程,关于丢包.拥塞 ...