递归算法

动态规划

早过忘川 提交于 2020-01-20 01:39:57
动态规划 Dynamic Programming 一种设计的技巧,是解决一类问题的方法 dp遵循固定的思考流程:暴力递归 —— 递归+记忆化 —— 非递归的动态规划(状态定义+转移方程) 斐波那契数列 暴力递归,看上去很简洁 def fib(n): return n if n <= 1 else fib(n-1) + fib(n-2) 画出递归树分析一下,可以很容易发现有很多重复计算。 重叠子问题 。 递归算法的时间复杂度怎么计算? 子问题个数乘以解决一个子问题需要的时间。显然,斐波那契数列的递归解法时间复杂度为O(2 n * 1),暴力递归解法基本都会超时。 如何解决? 递归 + 记忆化 仍然使用递归,不同点在于,如果重叠子问题已经计算过,就不用再算了,相当于对冗余的递归树进行了剪枝。 由于不存在重叠子问题,时间复杂度为O(n * 1),降到线性。 1 class Solution: 2 def Fibonacci(self, n): 3 # write code here 4 if n <= 1: 5 return n 6 memo = [-1] * (n+1) 7 memo[0], memo[1]= 0, 1 8 9 def helper(n, memo): 10 if memo[n] >= 0: 11 return memo[n] 12 memo[n] = helper(n

算法设计方法概览

僤鯓⒐⒋嵵緔 提交于 2020-01-19 00:49:21
算法设计方法概览 文章目录 算法设计方法概览 递归算法 什么是递归 定义及分类 直接递归 间接递归 尾递归 使用场景 递归模型 递归算法设计 递归与数学归纳法 第一数学归纳法 第二数学归纳法 递归算法设计的一般步骤 分治算法 分治法概述 使用场景 分治法的求解过程 蛮力法 蛮力法概述 使用场景 回溯法 问题的解空间 概述 种类 什么是回溯法 使用回溯法的一般步骤 分枝限界法 什么是分枝限界法 分枝限界法的设计思想 1. 设计合适的限界函数 2. 组织活结点表 3. 确定最优解的解向量 采用分枝限界法的三个关键问题 贪心法 贪心法概述 贪心法应用约束 贪心选择性质 最优子结构性质 动态规划 动态规划的原理 动态规划求解的基本步骤 动态规划与其他方法的比较 递归算法 什么是递归 定义及分类 直接递归 在定义一个过程或者函数时,出现调用本过程或本函数的成分,称之为递归。如果调用自身,称之为直接递归; 间接递归 若过程或者函数p调用过程或者函数q,而q又调用p,称之为间接递归; 任何间接递归都可以等价地转换为直接递归; 尾递归 如果一个递归过程或递归函数中递归调用语句是最后一条执行语句,则称这种递归为尾递归; 使用场景 可以使用递归解决的问题,应该满足以下三个特点: 需要解决的问题可以转换为一个或多个子问题来求解,而这些子问题的求解方法和原问题完全相同,只是数据规模不同;

递归与非递归及其相互转换

蓝咒 提交于 2020-01-18 15:48:13
一、什么是递归 递归是指某个函数直接或间接的调用自身。问题的求解过程就是划分成许多相同性质的子问题的求解,而小问题的求解过程可以很容易的求出,这些子问题的解就构成里原问题的解了。 二、递归的几个特点 1. 递归式,就是如何将原问题划分成子问题。 2. 递归出口,递归终止的条件,即最小子问题的求解,可以允许多个出口。 3. 界函数,问题规模变化的函数,它保证递归的规模向出口条件靠拢 三、递归的运做机制 很明显,很多问题本身固有的性质就决定此类问题是递归定义,所以递归程序很直接算法程序结构清晰、思路明了。但是递归的执行过程却很让人费解,这也是让很多人难理解递归的原因之一。由于递归调用是对函数自身的调用,在一次调用没有结束之前又开始了另外一次调用, 按照作用域的规定,函数在执行终止之前是不能收回所占用的空间,必须保存下来,这也就意味着每一次的调用都要把分配的相应空间保存起来。 为了更好管理这些空间,系统内部设置一个栈,用于存放每次函数调用与返回所需的各种数据, 其中主要包括函数的调用结束的返回地址,返回值,参数和局部变量等。 其过程大致如下: 1. 计算当前函数的实参的值 2. 分配空间,并将首地址压栈,保护现场 3. 转到函数体,执行各语句,此前部分会重复发生(递归调用) 4. 直到出口,从栈顶取出相应数据,包括,返回地址,返回值等等 5. 收回空间,恢复现场

一些常用的算法技巧总结

柔情痞子 提交于 2020-01-18 07:57:09
1. 巧用数组下标 数组的下标是一个隐含的很有用的数组,特别是在统计一些数字,或者判断一些整型数是否出现过的时候。例如,给你一串字母,让你判断这些字母出现的次数时,我们就可以把这些字母作为 下标 ,在遍历的时候,如果字母a遍历到,则arr[a]就可以加1了,即 arr[a]++; 通过这种巧用下标的方法,我们不需要逐个字母去判断。 我再举个例子: 问题: 给你n个无序的int整型数组arr,并且这些整数的取值范围都在0-20之间,要你在 O(n) 的时间复杂度中把这 n 个数按照从小到大的顺序打印出来。 对于这道题,如果你是先把这 n 个数先排序,再打印,是不可能O(n)的时间打印出来的。但是数值范围在 0-20。我们就可以巧用数组下标了。把对应的数值作为数组下标,如果这个数出现过,则对应的数组加1。 代码如下: public void f(int arr[]) { int[] temp = new int[21]; for (int i = 0; i < arr.length; i++) { temp[arr[i]]++; } //顺序打印 for (int i = 0; i < 21; i++) { for (int j = 0; j < temp[i]; j++) { System.out.println(i); } } } 提醒:可以左右滑动 利用数组下标的应用还有很多

二叉树---中续遍历(递归)

蓝咒 提交于 2020-01-17 23:04:25
@Adrian 二叉树中序遍历的实现思想是: 访问当前节点的左子树; 访问根节点; 访问当前节点的右子树; 以图 1 为例,采用中序遍历的思想遍历该二叉树的过程为: 访问该二叉树的根节点,找到 1; 遍历节点 1 的左子树,找到节点 2; 遍历节点 2 的左子树,找到节点 4; 由于节点 4 无左孩子,因此找到节点 4,并遍历节点 4 的右子树; 由于节点 4 无右子树,因此节点 2 的左子树遍历完成,访问节点 2; 遍历节点 2 的右子树,找到节点 5; 由于节点 5 无左子树,因此访问节点 5 ,又因为节点 5 没有右子树,因此节点 1 的左子树遍历完成,访问节点 1 ,并遍历节点 1 的右子树,找到节点 3; 遍历节点 3 的左子树,找到节点 6; 由于节点 6 无左子树,因此访问节点 6,又因为该节点无右子树,因此节点 3 的左子树遍历完成,开始访问节点 3 ,并遍历节点 3 的右子树,找到节点 7; 由于节点 7 无左子树,因此访问节点 7,又因为该节点无右子树,因此节点 1 的右子树遍历完成,即整棵树遍历完成; 二叉树采用中序遍历得到的序列为: 4 2 5 1 6 3 7 递归中序遍历: # include <stdio.h> # include <string.h> # define TElemType int //构造结点的结构体 typedef struct

javascript 算法 ---递归

老子叫甜甜 提交于 2020-01-17 18:18:56
“要理解递归,首先要理解递归。” ——佚名 递归函数是在函数内部能够直接或间接调用自身的方法或函数 。 假设一个函数一直调用自己结果是什么?单就上述情况而言,它会一直执 行下去。因此,每个递归函数都必须有基线条件,即一个不再递归调用的条件(停止点),以防 止无限递归。 function A(X) {   const recursionAnswer = confirm('Do you understand recursion?'); if (recursionAnswer === true) {     //基线条件 ,停止点    return true;  }   //递归调用   A(recursionAnswer); } 作为递归的第一个例子,我们来看看如何计算一个数的阶乘。数 n的阶乘,定义为 n!,表示 从 1到 n的整数的乘积。 5的阶乘表示为 5!,和 5 × 4 × 3 × 2 × 1相等,结果是 120。 (注意一个概念定义 0!= 1,不是等于 0) function factorial(x){ //基线 if(x == 0 || x==1){ return 1; } //递归调用 return x*factorial(x-1); } 斐波那契数列 0、1、1、2、3、5、8、13、21、 34等数组成的序列。数 2由 1 + 1得到,数 3由 1 + 2得到

快速排序

与世无争的帅哥 提交于 2020-01-17 17:13:03
快速排序 什么是快速排序 快速排序使用 分治法 (Divide and conquer)策略来把一个序列(list)分为较小和较大的2个子序列,然后递归地排序两个子序列。 步骤为: 挑选基准值:从数列中挑出一个元素,称为“基准”(pivot), 分割:重新排序数列,所有比基准值小的元素摆放在基准前面,所有比基准值大的元素摆在基准后面(与基准值相等的数可以到任何一边)。在这个分割结束之后,对基准值的排序就已经完成, 递归排序子序列:递归地将小于基准值元素的子序列和大于基准值元素的子序列排序。 递归到最底部的判断条件是数列的大小是零或一,此时该数列显然已经有序。 选取基准值有数种具体方法,此选取方法对排序的时间性能有决定性影响。 —— 维基百科 维基百科上说得很详细! 性能分析 时间复杂度 快速排序的一次划分算法从两头交替搜索,直到 l o w low l o w 和 h i g h high h i g h 重合,因此其时间复杂度是 O ( n ) O(n) O ( n ) ;而整个快速排序算法的时间复杂度与划分的趟数有关 理想的情况是,每次划分所选择的中间数恰好将当前序列几乎等分,经过 l o g 2 n log2n l o g 2 n 趟划分,便可得到长度为1的子表。这样,整个算法的时间复杂度为 O ( n l o g 2 n ) O(nlog2n) O ( n l o g 2

leetcode刷题记第17题解法(python解析)

空扰寡人 提交于 2020-01-17 02:08:01
leetcode刷题记--> 17题解法(python解析) 题目定义 解题 1. 使用reduce方法进行解决问题 2. 动态规划 3. 动态规划 4. 使用递归 实现 题目定义 给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。 给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。 示例: 输入:“23” 输出:[“ad”, “ae”, “af”, “bd”, “be”, “bf”, “cd”, “ce”, “cf”]. 说明: 尽管上面的答案是按字典序排列的,但是你可以任意选择答案输出的顺序。 来源:力扣(LeetCode) 链接: leetcode_17题 . 解题 本次使用4种方法,在leetcode上还有更多的方法,只能说真牛逼,真聪明。 1. 使用reduce方法进行解决问题 熟悉大数据的同学都知道map reduce 此处就使用reduce进行解决 2. 动态规划 使用列表生成式 3. 动态规划 同样是列表生成式,比较绕,仔细理解 4. 使用递归 对于打印"2345"这样的字符串: 第一次递归就是上图中最下面的方格,然后处理完第一个字符2之后,将输入的字符改变成"345"并调用第二个递归函数 第二次递归处理3,将字符串改变成"45"后再次递归 第三次递归处理4,将字符串改变成"5"后继续递归 第四次递归处理5,将字符串改变成"

【算法导论】第4章 分而治之 (3)

谁都会走 提交于 2020-01-17 00:19:54
Introduction to Algorithms - Third Edition Part I. Foundations Chapter 4. Divide-and-Conquer ★ \bigstar ★ 4.6 主定理的证明 4.6.1 取正合幂时的证明 假设 n n n 是 b > 1 b>1 b > 1 的正合幂,其中 b b b 不必是整数,分析主方法中的递归式 (4.20) T ( n ) = a T ( n / b ) + f ( n ) T(n) = aT(n/b) + f(n) T ( n ) = a T ( n / b ) + f ( n ) 。 将分析分成 3 个引理来说明。 第1个引理,将求解主递归式的问题,归约为对包含总和的表达式求值的问题。 第2个引理,确定这个总和的界限。 第3个引理,将前两个结合一起,证明:在 n n n 是 b b b 的正合幂的情况下,主定理成立。 引理 4.2 设 a ≥ 1 a \ge 1 a ≥ 1 和 b > 1 b > 1 b > 1 是常数,设 f ( n ) f(n) f ( n ) 是定义在 b b b 的正合幂上的非负函数。在 b b b 的正合幂上定义 T ( n ) T(n) T ( n ) : T ( n ) = { Θ ( 1 ) if n = 1 a T ( n / b ) + f ( n ) if

算法五:快速排序

自作多情 提交于 2020-01-16 14:58:35
快速排序是对冒泡排序的一种改进。思想:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小, 然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序。 选择一个基准数,这个数的左边都比她小,右边的都比它大,然后再递归处理左右两边的操作直到左右的各区只有一个数 数组: 3,2,1,6,9 #include <iostream> using namespace std; void Qsort(int a[], int low, int high) { if (low >= high) return; int frist = low; int last = high; int key = a[frist]; while (frist < last) { while (frist < last &&a[last] > key) --last; a[frist] = a[last]; while (frist < last && a[frist] < key) ++frist; a[last] = a[frist]; } a[frist] = key; Qsort(a, low, frist); Qsort(a, frist + 1,high); } int main() { int a[] = {3,1