递归调用

算法导论基础(第一~五章)

心不动则不痛 提交于 2019-11-26 14:57:54
插入排序 最好情况输入数组开始时候就是满足要求的排好序的,时间代价为 θ(n); 最坏情况输入数组是按逆序排序的,时间代价为 θ(n^2)。 归并排序 归并排序采用了算法设计中的分治法,分治法的思想是将原问题分解成n个规模较小而结构与原问题相似的小问题,递归的解决这些子问题,然后再去合并其结果,得到原问题的解。 分治模式在每一层递归上有三个步骤: 分解(divide):将原问题分解成一系列子问题。 解决(conquer):递归地解答各子问题,若子问题足够小,则直接求解。 合并(combine):将子问题的结果合并成原问题的解。 归并排序(merge sort)算法按照分治模式,操作如下: 分解:将n个元素分解成各含n/2个元素的子序列 解决:用合并排序法对两个序列递归地排序 合并:合并两个已排序的子序列以得到排序结果 算法中含有对其自身的递归调用,其运行时间可以用一个递归方程(或递归式)来表示。归并排序算法分析采用递归树进行,递归树的层数为lgn+1,每一层的时间代价是cn,整棵树的代价是cn(lgn+1)=cnlgn+cn,忽略低阶和常量c,得到结果为 θ(nlg n)。 转载于:https://www.cnblogs.com/lucas-hsueh/p/3731403.html 来源: https://blog.csdn.net/weixin_30586257/article

数据-第18课-栈与递归

旧时模样 提交于 2019-11-26 13:51:27
第18课-栈与递归 C语言中的疑惑 讨论中…… 小A:C语言中常说“局部变量在栈上分配空间”,那么这个地方的“栈”和我们之前学习的栈数据结构有关系吗? 小B:我觉得应该没关系吧 :我觉得应该没关系吧,只是名称碰巧一致而已吧?! 1. 函数调用时的栈 (1)程序中的“函数调用栈”是栈数据结构的一种应用。 (2)函数调用栈一般是从高地址向低地址增长的。 l 栈底为内存的高地址处。 l 栈顶为内存的低地址处。 (3)函数调用栈中存储的数据为活动记录 2. 活动记录 活动记录是函数调用时一系列相关信息的记录。 3. 函数调用过程 4. 程序中的栈 (1) 程序中的栈空间可看做一个顺序栈的应用。 (2) 栈保存了一个函数调用所需的维护信息。 (3) 函数参数,函数返回地址 ,函数返回地址。 l 局部变量。 l 函数调用上下文。 5. 栈的溢出 (1) 在不断的压栈过程中造成栈空间耗尽而产生栈溢出。 (2) 栈溢出常由于函数递归过深或局部数组过大造成。 #include <stdio.h> void reverse(char* s) { if( (s != NULL) && (*s != '\0') ) { reverse(s + 1); printf("%c", *s); } } int main() { reverse("12345"); printf("\n"); return 0; }

PL真有意思(四):控制流

我只是一个虾纸丫 提交于 2019-11-26 13:06:34
前言 对大多数计算模型而言,顺序都是基本的东西,它确定了为完成所期望的某种工作,什么事情应该最先做,什么事应该随后做,我们可以将语言规定顺序的机制分为几个类别: 顺序执行 选择 迭代 过程抽象 递归 并发 异常处理和推断 非确定性 对于不同类别的语言对不同类别的控制流的重要性也不尽相同,比如顺序执行相比于函数式对于命令式则更加重要。而命令式中更倾向用迭代,函数则更强调递归 表达式求值 在讨论控制流之前先讨论下表达式的问题,先明确两个概念:运算符通常是指那些采用特殊语法形式的内部函数(比如+-*/等),运算对象指的是运算符的参数(如2+3,2和3就是运算对象),那么运算符和运算对象的组合就是表达式。一般根据运算符出现的位置(相对于运算对象而言),可以分为3类表示形式:前缀、中缀和后缀。比如Lisp就运用前缀语法: (+ 1 3 4 6) (* (+ 1 7) 8) 大多数命令式语言对二元运算符都使用中缀记法,而对一元运算符和其它函数使用前缀激发。但是像Lisp就全部统一使用中缀记法 优先级和结合性 大多数程序设计语言都提供丰富的内部算术。在用中缀方式(没有括号)写出就可能出现歧义。所以就需要优先级和结合性来解决歧义性,但是我觉得 妈的你写括号就完事儿了 而且不同语言的优先级和结合性也不尽相同 赋值 在纯函数式语言中,程序的基本组成部分是表达式,计算也仅是对表达式求值

分治法的概念以及应用

99封情书 提交于 2019-11-26 12:08:12
分治法:“分久必合,合久必分” 哈哈,其实分治法应该理解为分而治之的方法,它的基本思想是把一个大的问题比较复杂的问题,拆分成多个规模较小的子问题,然后解决这些子问题的难度就比原来大的问题简单的多。但是这个拆分是要注意如何去拆分按什么思路去拆分因为拆分出来的这种子问题要求和原来的问题是同样的结构的问题(也可以说是相同的问题),只是复杂度小一些规模小一些,拆分出来的子问题又可以用同样的方式进一步的拆分。那么既然如此我们解决这个问题时候所采用的函数是不是能够用同样的函数去解决问题呢?答案是肯定的。所以再分治法中往往要用到递归的技术来解决问题。递归技术在下文单独篇。以上就是我对分治法基本思想的理解。 所以分治法它会先做分解,分解之后它会把一系列小的问题给它解决掉,然后再把小的问题进行相应的汇合,一步一步整合合并,合并之后得到最终的原来的初始问题的解决方案。就是按这种思路来解决问题,要利用到分治法来解决问题往往要对这个问题本身有一定的要求,首先这个问题它的规模缩小到一定的程度要能够容易的解决,否则就失去了拆分它的意义。其次这个问题必须要能够分解成若干个规模较小的相同问题,如果分出来的是不同问题那也不行。最后要能够利用到这个问题,分解出来的子问题的解能够合并为原来问题的解。同时各个子问题要求是相互独立的,不相互独立也不行。这就是分治法的基本思想以及对分治法应用的基本要求。 递归技术

实验二 递归下降语法分析

丶灬走出姿态 提交于 2019-11-26 12:00:30
老师: MissDu  提交作业 一、实验目的: 利用C语言编制递归下降分析程序,并对简单语言进行语法分析。 编制一个递归下降分析程序,实现对词法分析程序所提供的单词序列的语法检查和结构分析。 二、实验原理 每个非终结符都对应一个子程序。 该子程序根据下一个输入符号(SELECT集)来确定按照哪一个产生式进行处理,再根据该产生式的右端: 每遇到一个终结符,则判断当前读入的单词是否与该终结符相匹配,若匹配,再读取下一个单词继续分析;不匹配,则进行出错处理 每遇到一个非终结符,则调用相应的子程序 三、实验要求说明 输入单词串,以“#”结束,如果是文法正确的句子,则输出成功信息,打印“success”,否则输出“error”,并指出语法错误的类型及位置。 例如: 输入begin a:=9;x:=2*3;b:=a+x end # 输出success 输入x:=a+b*c end # 输出‘end' error 四、实验步骤 1.待分析的语言的语法(参考P90) 2.将其改为文法表示,至少包含 –语句 –条件 –表达式 3. 消除其左递归 4. 提取公共左因子 5. SELECT集计算 6. LL(1)文法判断 7. 递归下降分析程序 #include<iostream> #include<stdio.h> #include<string> using namespace std;

递归函数

怎甘沉沦 提交于 2019-11-26 11:02:46
我们在前面的章节中,很多次的看到了在函数中调用别的函数的情况。如果一个函数在内部调用了自身,这个函数就被称为递归函数。 What?函数可以自己调用自己?那不是成为了“衔尾蛇”?会不会进入死循环,永远退出不了?我们先看一个例子,典型的高斯求和问题,1+2+3+4+…+99+100,不使用递归的话,我们可以用循环,这么做: def sum_number(n): """ 1-100相加 :param n: :return: """ total = 0 for i in range(1, n + 1): total += i return total print(sum_number(100)) 但如果使用递归函数来写,是这样的: def sum_number(n): """ 递归函数1——100 :param n: :return: """ if n <= 0: return 0 return n + sum_number(n - 1) print(sum_number(100)) 分析一下代码,当n小于等于0的时候,直接给出和值为0,这句不能省。当n大于0时,结果是n加上 sum_number(n-1) 。这里的 sum_number(n-1) 又是一次 sum_number 函数的调用,不过参数的值变成了n-1,要得 sum_number(n) 到的值就必须等待 sum_number

算法笔记——递归练习

你离开我真会死。 提交于 2019-11-26 05:58:42
1、分治思想与递归手段 首先,分治的英文是divide and conquer, 也就是分而治之的意思,基本策略是将原问题划分为若干个规模较小而结构与原问题相同或相似的子问题,然后分别解决这些子问题,最后合并子问题的解就能得到原问题的解。 一般我们都是使用递归的手段去实现分治思想 2、递归 概念:通过反复调用自身函数, 但是每次都把问题范围缩小,直到范围缩小到可以直接得到边界数据的结果,然后再在返回的路上求出结果。正是因为递归与分治的思想非常接近,所以我们会递归来解决分治问题。 递归的两个重要概念:递归边界与递归式。递归式是将原问题分解为若干个子问题的手段,递归边界则是分解的尽头 3、递归的具体例子练习 阶乘: n!= n * n-1 * n-1 * ... * 1, 写成递归式就是 n! = ( n-1 ) ! * n, 可以看出跳出递归的边界就是n为1或0的情况(因为1!=0!=0 ,所以都可以作为递归边界) #include<stdio.h> #include<iostream> using namespace std; int f(int n) { if(n == 0) return 1; //这个判定就是递归边界 else return f(n-1) * n; //f(n) = f(n-1) * n就是递归式 } int main() { int n; cin>>n;

静态,非静态,递归

≯℡__Kan透↙ 提交于 2019-11-26 00:32:15
#静态与非静态 static表示静态的。 他需要常驻内存,一直占用内存,无法进行垃圾回收,需慎用,他在那种情况适用呢? 不管哪个对象调用,结果都是一样的(这个结果的一致指的是你输出的数据图案的一致性等等),调用可直接用类名字调用,不许要变量名了。和对象就没有关系了。 之前所说的对象是是Java中所面对的东西,也就是你要用程序解决问题的哪一个,Java是面对对象的语言, 建立方法最好是单独定义一个类,类里面存在共同的特征。就相当于一个模板 当你建立方法的时候你最好单独把方法集中放在一个类中,,这样不同的对象皆可应用这个类,用 “ ,“表示所属关系 静态方法和非静态方法的调用内存方式是不一样的,静态得是占了一个存储空间,他就不变了,而非静态是随你调用方法(通过new调用构造方法)时开始开辟内存,一个变量对应一个指定的内存,当你调用完以后,你开辟的内存随之消失, ##静态方法(static method) 与静态成员变量一样,属于类本身,在类装载的时候被装载到内存中,不自动进行销毁,会一直存在内存中,直到JVM关闭; ##非静态方法(non-static method) 又称实例化方法,属于实例对象,实例化之后才会分配内存,必须通过类的实例来引用,当实例对象被JVM回收之后,也跟着消失 ##静态方法和实例方法的区别 1.生命周期 静态方法的生命周期从进程创建时就开始,一直到进程结束

006_函数

安稳与你 提交于 2019-11-25 20:18:57
return 的功能: ①返回值;②结束所在方法 return 与 break 的区别: break 是结束本层循环; return 是结束所在的整个方法,return之后的代码不会执行。 递归 递归的理解:把一个大问题拆分成一个个小问题,解决的方法相同,有固定规律,自己调用自己。 避免无穷递归使用条件: ①得有出口条件 ②自己调用自己 ③每次分解都比上一次简单,朝着出口方向递进 来源: https://www.cnblogs.com/a276665092/p/11929921.html

为什么你学不会递归?告别递归,谈谈我的经验

吃可爱长大的小学妹 提交于 2019-11-25 19:52:38
可能很多人在大一的时候,就已经接触了递归了,不过,我敢保证很多人初学者刚开始接触递归的时候,是一脸懵逼的,我当初也是,给我的感觉就是,递归太神奇了! 可能也有一大部分人知道递归,也能看的懂递归,但在实际做题过程中,却不知道怎么使用,有时候还容易被递归给搞晕。也有好几个人来问我有没有快速掌握递归的捷径啊。说实话,哪来那么多捷径啊,不过,我还是想写一篇文章,谈谈我的一些经验,或许,能够给你带来一些帮助。 为了兼顾初学者,我会从最简单的题讲起! 递归的三大要素 第一要素:明确你这个函数想要干什么 对于递归,我觉得很重要的一个事就是, 这个函数的功能是什么 ,他要完成什么样的一件事,而这个,是完全由你自己来定义的。也就是说,我们先不管函数里面的代码什么,而是要先明白,你这个函数是要用来干什么。 例如,我定义了一个函数 // 算 n 的阶乘(假设n不为0) int f(int n){ } 这个函数的功能是算 n 的阶乘。好了,我们已经定义了一个函数,并且定义了它的功能是什么,接下来我们看第二要素。 第二要素:寻找递归结束条件 所谓递归,就是会在函数内部代码中,调用这个函数本身,所以,我们必须要找出 递归的结束条件 ,不然的话,会一直调用自己,进入无底洞。也就是说,我们需要找出 当参数为啥时,递归结束,之后直接把结果返回 ,请注意,这个时候我们必须能根据这个参数的值,能够 直接