面试时写不出排序算法?看这篇就够了(下)

人工智能
• 阅读 171

前言

递归是一种解决问题的方法,它解决问题的各个小部分,直到解决最初的大问题。通常涉及函数调用自身。

能够像下面这样直接调用自身的方法或函数,是递归函数:

面试时写不出排序算法?看这篇就够了(下)

能够像下面这样间接调用自身的函数,也是递归函数:

面试时写不出排序算法?看这篇就够了(下)

假设现在必须要执行recursiveFunction,结果是什么?单单就上述情况而言,它会一直 执行下去。因此,每个递归函数都必须要有边界条件,即一个不再递归调用的条件(停止点), 以防止无限递归。

JavaScript调用栈大小的限制

如果忘记加上用以停止函数递归调用的边界条件,会发生什么呢?递归并不会无限地执行下 去;浏览器会抛出错误,也就是所谓的栈溢出错误(stack overflow error)。每个浏览器都有自己的上限,可用以下代码测试:

面试时写不出排序算法?看这篇就够了(下)

在Chrome v37中,这个函数执行了20955次,而后浏览器抛出错误RangeError: Maximum call stack size exceeded(超限错误:超过最大调用栈大小)。Firefox v27中,函数执行了343429次,然后浏览器抛出错误 InternalError: too much recursion(内部错误:递归次数过多)。

根据操作系统和浏览器的不同,具体数值会所有不同,但区别不大。

ECMAScript 6有尾调用优化(tail call optimization)。如果函数内最后一个操作是调用函数(就 像示例中加粗的那行),会通过“跳转指令”(jump) 而不是“子程序调用”(subroutine call)来 控制。也就是说,在ECMAScript 6中,这里的代码可以一直执行下去。所以,具有停止递归的边 界条件非常重要。

有关尾调用优化的更多相关信息,请访问_http://goo.gl/ZdTZzg。_

动态规划

动态规划(Dynamic Programming,DP)是一种将复杂问题分解成更小的子问题来解决的优化技术。

要注意动态规划和分而治之(归并排序和快速排序算法中用到的那种)是不同的方法。分而治之方法是把问题分解成相互独立的子问题,然后组合它们的答案,而动态规划则是将问题分解成相互依赖的子问题。

用动态规划解决问题时,要遵循三个重要步骤:

1. 定义子问题;

2.实现要反复执行而解决子问题的部分

3. 识别并求解出边界条件。

能用动态规划解决的一些著名的问题如下。

背包问题:给出一组项目,各自有值和容量,目标是找出总值最大的项目的集合。这个 问题的限制是,总容量必须小于等于“背包”的容量。

最长公共子序列:找出一组序列的最长公共子序列(可由另一序列删除元素但不改变余 下元素的顺序而得到)。

矩阵链相乘:给出一系列矩阵,目标是找到这些矩阵相乘的最高效办法(计算次数尽可 能少)。相乘操作不会进行,解决方案是找到这些矩阵各自相乘的顺序。

硬币找零:给出面额为d1…dn的一定数量的硬币和要找零的钱数,找出有多少种找零的 方法。

图的全源最短路径:对所有顶点对(u, v),找出从顶点u到顶点v的最短路径。

接下来的例子,涉及硬币找零问题的一个变种。

最少硬币找零问题

最少硬币找零问题是硬币找零问题的一个变种。硬币找零问题是给出要找零的钱数,以及可 用的硬币面额d1…dn及其数量,找出有多少种找零方法。最少硬币找零问题是给出要找零的钱数, 以及可用的硬币面额d1…dn及其数量,找到所需的最少的硬币个数。

例如,美国有以下面额(硬币):d1=1,d2=5,d3=10,d4=25。

如果要找36美分的零钱,我们可以用1个25美分、1个10美分和1个便士(1美分)。

如何将这个解答转化成算法?

最少硬币找零的解决方案是找到n所需的最小硬币数。但要做到这一点,首先得找到对每个 x<n的解。然后,我们将解建立在更小的值的解的基础上。

来看看算法:

面试时写不出排序算法?看这篇就够了(下)

为了更有条理,我们创建了一个类,解决给定面额的最少硬币找零问题。让我们一步步解读这个算法。

MinCoinChange类接收coins参数(行{1}),代表问题中的面额。对美国的硬币系统而言, 它是[1, 5, 10, 25]。我们可以随心所欲传递任何面额。此外,为了更加高效且不重复计算值, 我们使用了cache(行{2})。

接下来是makeChange方法,它也是一个递归函数,找零问题由它解决。首先,若amount 不为正(< 0),就返回空数组(行{3});方法执行结束后,会返回一个数组,包含用来找零的各 个面额的硬币数量(最少硬币数)。接着,检查cache缓存。若结果已缓存(行{4}),则直接返 回结果;否则,执行算法。

我们基于coins参数(面额)解决问题。因此,对每个面额(行{5}),我们都计算newAmount (行{6})的值,它的值会一直减小,直到能找零的最小钱数(别忘了本算法对所有的x < amount 都会计算makeChange结果)。若newAmount是合理的值(正值),我们也会计算它的找零结果(行 {7})。

最后,我们判断newAmount是否有效,minValue (最少硬币数)是否是最优解,与此同时 minValue和newAmount是否是合理的值({行10})。若以上判断都成立,意味着有一个比之前 更优的答案(行{11},以5美分为例,可以给5便士或者1个5美分镍币,1个5美分镍币是最优解)。最后,返回最终结果(行{12})。

测试一下这个算法:

面试时写不出排序算法?看这篇就够了(下)

要知道,如果我们检查cache变量,会发现它存储了从1到36美分的所有结果。以上代码的 结果是[1, 10, 25]。

作者:技术全能渣男
链接:https://segmentfault.com/a/11...
来源:SegmentFault 思否
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

如果喜欢我,可以关注下面的公众号,了解更多干货的同时,还可以和上千的技术大咖一起交流讨论~

面试时写不出排序算法?看这篇就够了(下)

点赞
收藏
评论区
推荐文章
美凌格栋栋酱 美凌格栋栋酱
7个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
凯特林 凯特林
4年前
浅谈JS中的递归
一、递归递归(英语:Recursion)在数学与计算机科学中,是指在函数的定义中使用函数自身的方法在函数内部,可以调用其他函数。如果一个函数在内部调用自身本身,这个函数就是递归函数其核心思想是把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解一般来说,递归需要有边界条件、递归前进阶段和递归返回阶段。当边界条件不满足时,递归前进;当边界条件满
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
徐小夕 徐小夕
4年前
如何优雅的使用javascript递归画一棵结构树
递归和尾递归简单的说,递归就是函数自己调用自己,它做为一种算法在程序设计语言中广泛应用。其核心思想是把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的
Irene181 Irene181
4年前
一篇文章带你了解Python递归函数
一、什么是递归函数?在函数内部,可以调用其他函数。如果一个函数在内部调用自身本身,这个函数就是递归函数。二、函数的递归调用原理实际上递归函数是在栈内存上递归执行的,每次递归执行一次就会耗费一些栈内存。栈内存的大小是限制递归深度的重要因素三、案例分析1.求阶乘计算阶乘n!1x2x3x…xn,可以用
Wesley13 Wesley13
3年前
!浅识!shell函数及数组
Shell函数及数组SHELL函数函数的用法基本格式函数的调用示例函数变量的作用范围示例函数的参数递归函数SHELL数组数组定义的方法数组的基本使用方法:1.获取数组长度2.读取某下标赋值
Stella981 Stella981
3年前
Python之函数递归与迭代
函数递归:  定义:程序调用自身的编程技巧称为递归(recursion)。递归做为一种算法在程序设计语言中广泛应用。一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量
Wesley13 Wesley13
3年前
PHP快速排序(原地切分)
        快速排序是一种分治的排序算法,采用递归的思想,将数组元素分为两部分,选择切分元素,左右扫描数组,将大于切分元素的数据放在右边,小于切分元素的数据放在左边,直到扫描指针相遇,切分结束,同时递归调用,直到数组有序。      代码如下:<?phpfunctionquick_sort(array&$array,$l
Wesley13 Wesley13
3年前
JAVA学习入门篇_递归结构
递归结构递归是一种常见的解决问题的方法,即把问题逐渐简单化。递归的基本思想就是“自己调用自己”,一个使用递归技术的方法将会直接或者间接的调用自己。​利用递归可以用简单的程序来解决一些复杂的问题。比如:斐波那契数列的计算、汉诺塔、快排等问题。​递归结构包括两个部分:​1.定义递归头。解答:什么
小万哥 小万哥
1年前
C++ 递归与面向对象编程基础
C递归递归是一种使函数调用自身的技术。这种技术提供了一种将复杂问题分解为简单问题的方法,从而更容易解决问题。递归可能有点难以理解。理解其工作原理的最佳方法是通过实验来尝试。递归示例将两个数字相加很容易做到,但将一系列数字相加就更复杂了。在下面的示例中,
贾蔷 贾蔷
3个月前
力扣145题:二叉树的后序遍历, 解题思路与C++实现
题目介绍力扣第145题要求实现一个函数,该函数接收一个二叉树的根节点,并返回该树的后序遍历结果。后序遍历是一种遍历二叉树的算法,其顺序为:先遍历左子树,是右子树,是根节点。解题思路分析解题时,我们可以使用递归或迭代的方法。递归方法较为直观,但可能导致栈溢出