分治法和递归

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

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

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

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

一、递归

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

比如我们求阶乘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. JAVA开发中如何优化类的设计

    具体类依赖于抽象类,而非抽象类依赖于具体类.这样做有利于一个抽象类扩展多个具体类. 开放封闭原则:对扩展开放,对修改封闭. 1.永远保持数据私有 保持数据的私有是设计类时,必须重点考虑的问题.保持私有 ...

  2. jwt token认证

    目录 1.drf-jwt手动签发与校验 2.drf小组件:过滤.筛选.排序.分页 => 针对与群查接口 jwt_token源码分析(入口) 签发token源码分析 校验token源码分析 url ...

  3. 《闲扯Redis一》五种数据类型之String型

    一.前言 Redis 提供了5种数据类型:String(字符串).Hash(哈希).List(列表).Set(集合).Zset(有序集合),理解每种数据类型的特点对于redis的开发和运维非常重要. ...

  4. 最大比率传输(Maximum Ratio Transmission, MRT)原理分析

    转载请注明出处. 最大比率发射(Maximum Ratio Transmission, MRT)是文献中经常看见的一个词,今天就在这里做一下笔记. 参考文献为:T. K. Y. Lo, "M ...

  5. 主从校验工具pt-table-checksum和pt-table-sync工作原理

    pt-table-checksum和pt-table-sync是常用来做MySQL主从数据一致性校验的工具,pt-table-checksum只校验数据,不能对数据进行同步:pt-table-sync ...

  6. mongodb_2

    一.游标 在mongodb中,底层使用js引擎进行各种操作,所以我们在命令行窗口,可直接执行js代码. #使用for循环,插入1000条数据. > for (var i=0;i<1000; ...

  7. 【WPF学习】第六十章 创建控件模板

    经过数十天的忙碌,今天终于有时间写博客. 前面一章通过介绍有关模板工作方式相关的内容,同时介绍了FrameWorkElement下所有控件的模板.接下来将介绍如何构建一个简单的自定义按钮,并在该过程中 ...

  8. ElasticSearch 倒排索引

    倒排索引 倒排表以字或词为关键字进行索引,表中关键字所对应的记录表项记录了出现这个字或词的所有文档,一个表项就是一个字表段,它记录该文档的ID和字符在该文档中出现的位置情况. 由于每个字或词对应的文档 ...

  9. 原来rollup这么简单之 tree shaking篇

    大家好,我是小雨小雨,致力于分享有趣的.实用的技术文章. 内容分为翻译和原创,如果有问题,欢迎随时评论或私信,希望和大家一起进步. 分享不易,希望能够得到大家的支持和关注. 计划 rollup系列打算 ...

  10. html+css实现图片或元素的垂直、水平同时居中的多种方法

    实现元素或图片的上下.左右居中的三种方法 效果图如下: 方法一:利用vertical-align属性实现图片上下居中 先设置父元素样式text-align: center,实现图片左右居中,给图片添加 ...