第六章 递归与分治法
算法设计与分析(王晓东)

a b a b
(2)方法重载:Java允许方法重载,即允许定义有不同签名的同名方法。
上述方法ab可重载为:
public static double ab(double a, double b) { return (a+b+Math.abs(a-b))/2.0; } 12
4.异常
1.3 描述算法
6
1.2 表达算法的抽象机制
2.抽象数据类型
抽象数据类型是算法的一个数据模型连同定义在该模型上 并作为算法构件的一组运算。
抽象数据类型带给算法设计的好处有:
(1)算法顶层设计与底层实现分离; (2)算法设计与数据结构设计隔开,允许数据结构自由选择; (3)数据模型和该模型上的运算统一在ADT中,便于空间和时间耗费的折衷; (4)用抽象数据类型表述的算法具有很好的可维护性; (5)算法自然呈现模块化; (6)为自顶向下逐步求精和模块化提供有效途径和工具; (7)算法结构清晰,层次分明,便于算法正确性的证明和复杂性的分析。
中国计算机学会 “21世纪大学本科计算机专业系列教材”
算法设计与分析
王晓东 编著
1
主要内容介绍
• • • • • • 第1章 第2章 第3章 第4章 第5章 第6章 算法引论 递归与分治策略 动态规划 贪心算法 回溯法 分支限界法
递归与分治算法心得

递归与分治算法心得
递归与分治算法都是常用的算法思想,可以很好地解决复杂问题。
递归算法是通过将问题分解为相同或相似的子问题来解决整个问题,然后再逐步合并回原问题的过程。
递归算法通常需要明确边界条件,以确保递归能够正确地停止。
分治算法是将问题分解成若干个相同或相似的子问题,递归地解决这些子问题,然后合并这些子问题的解来解决原始问题。
通常,分治算法可以高效地解决问题,但需要注意分解问题的方式和合并子问题的解的过程。
在实际应用中,递归和分治算法可以相互结合,以解决更加复杂的问题。
例如,可以使用分治算法来将问题分解成多个子问题,然后使用递归算法来解决这些子问题。
此外,还可以在递归算法中使用分治算法来对子问题进行分解和合并。
总而言之,递归与分治算法都是非常有用的算法思想,可以在许多领域中得到应用。
但是,在实际使用时,需要仔细考虑问题的性质和算法的复杂度,以确保算法的正确性和效率。
- 1 -。
递归与分治算法心得

递归与分治算法心得
递归与分治算法是算法设计中常见的两种方法,它们在解决问题时都采用了“分而治之”的思想,将问题分解成更小的子问题,然后通过递归调用或者合并子问题的解来得到原问题的解。
通过我的学习和实践,我深刻认识到了递归与分治算法的重要性和优势。
首先,递归算法可以使问题的描述更加简单明了。
通过将问题转化为自身的子问题,我们可以建立起更为简洁优美的数学模型。
其次,递归算法可以使问题的解决过程更加自然。
在递归过程中,我们可以利用已知的子问题解决同类问题,实现代码的复用和模块化。
此外,递归算法还可以解决一些重要的数学问题,如斐波那契数列和二分查找等。
分治算法则更加注重问题的分解和合并。
它将问题划分成若干个规模相同或相近的子问题,然后将子问题的解合并起来得到原问题的解。
这种方法在解决某些复杂问题时具有很大的优势。
例如,在排序算法中,归并排序采用了分治算法的思想,将待排序的序列分成两个长度相等的子序列,然后递归地对子序列排序,最后将子序列合并成有序序列。
这种算法具有较高的稳定性和灵活性,常常被应用于海量数据的排序任务中。
总之,递归与分治算法是算法设计中不可或缺的两种方法。
在解决问题时,我们应该根据具体情况选择合适的算法,并在实践中不断探索、总结和优化。
只有这样,我们才能更好地应对日益复杂多变的计算机科学挑战。
分治法解决问题的步骤

分治法解决问题的步骤一、基础概念类题目(1 - 5题)题目1:简述分治法解决问题的基本步骤。
解析:分治法解决问题主要有三个步骤:1. 分解(Divide):将原问题分解为若干个规模较小、相互独立且与原问题形式相同的子问题。
例如,对于排序问题,可将一个大的数组分成两个较小的子数组。
2. 求解(Conquer):递归地求解这些子问题。
如果子问题规模足够小,则直接求解(通常是一些简单的基础情况)。
对于小到只有一个元素的子数组,它本身就是有序的。
3. 合并(Combine):将各个子问题的解合并为原问题的解。
在排序中,将两个已排序的子数组合并成一个大的有序数组。
题目2:在分治法中,分解原问题时需要遵循哪些原则?解析:1. 子问题规模更小:分解后的子问题规模要比原问题小,这样才能逐步简化问题。
例如在归并排序中,不断将数组对半分,子数组的长度不断减小。
2. 子问题相互独立:子问题之间应该尽量没有相互依赖关系。
以矩阵乘法的分治算法为例,划分后的子矩阵乘法之间相互独立进行计算。
3. 子问题与原问题形式相同:方便递归求解。
如二分查找中,每次查找的子区间仍然是一个有序区间,和原始的有序区间查找问题形式相同。
题目3:分治法中的“求解”步骤,如果子问题规模小到什么程度可以直接求解?解析:当子问题规模小到可以用简单的、直接的方法(如常量时间或线性时间复杂度的方法)解决时,就可以直接求解。
例如,在求数组中的最大最小值问题中,当子数组只有一个元素时,这个元素既是最大值也是最小值,可以直接得出结果。
题目4:分治法的“合并”步骤有什么重要性?解析:1. 构建完整解:它将各个子问题的解组合起来形成原问题的解。
例如在归并排序中,单独的两个子数组排序好后,只有通过合并操作才能得到整个数组的有序排列。
2. 保证算法正确性:如果合并步骤不正确,即使子问题求解正确,也无法得到原问题的正确答案。
例如在分治算法计算斐波那契数列时,合并不同子问题的结果来得到正确的斐波那契数是很关键的。
递归和分治法

递归和分治法摘要:1.递归和分治法的定义2.递归和分治法的区别3.递归和分治法的应用实例4.递归和分治法的优缺点正文:递归和分治法是计算机科学中常用的两种算法设计技巧。
它们在解决问题时都采用了将问题分解成更小子问题的思路,但在具体实现上却有所不同。
下面,我们来详细了解一下递归和分治法。
1.递归和分治法的定义递归法是指在算法中调用自身来解决问题的方法。
递归函数在执行过程中,会将原问题分解成规模更小的相似子问题,然后通过调用自身的方式,解决这些子问题,最后将子问题的解合并,得到原问题的解。
分治法是指将一个大问题分解成若干个规模较小的相似子问题,然后分别解决这些子问题,最后将子问题的解合并,得到原问题的解。
分治法在解决问题时,通常需要设计一个主函数(master function)和一个子函数(subfunction)。
主函数负责将问题分解,子函数负责解决子问题。
2.递归和分治法的区别递归法和分治法在解决问题时都采用了将问题分解成更小子问题的思路,但它们在实现上存在以下区别:(1)函数调用方式不同:递归法是通过调用自身来解决问题,而分治法是通过调用不同的子函数来解决问题。
(2)递归法必须有递归出口,即必须有一个基线条件,而分治法不一定需要。
3.递归和分治法的应用实例递归法应用广泛,例如斐波那契数列、汉诺塔问题、八皇后问题等。
分治法也有很多实际应用,例如快速排序、归并排序、大整数乘法等。
4.递归和分治法的优缺点递归法的优点是代码简单易懂,但缺点是容易产生大量的重复计算,导致时间复杂度较高。
分治法的优点是时间复杂度较低,但缺点是代码实现相对复杂,需要设计主函数和子函数。
总之,递归和分治法都是解决问题的有效方法,具体应用需要根据问题的特点来选择。
计算机算法的数学原理

计算机算法的数学原理计算机算法的数学原理计算机算法是计算机科学中的核心概念之一。
算法是一种规定好的计算步骤,用来解决特定问题的方法。
算法的正确性、高效性和可扩展性非常重要,这些关键因素都与数学原理紧密相连。
因此,本文将讨论计算机算法的数学原理,重点探讨算法设计、分析和应用方面的数学,希望能对读者有所启示。
第一部分:算法设计算法设计是计算机算法中的重要部分,它需要设计者具有数学思维和计算思维。
在设计算法时,需要考虑输入、输出、执行步骤和运行时间等方面。
其中,输入是指算法能够处理的数据类型和数据格式,输出是指算法输出的结果类型和格式,执行步骤是指算法执行的操作和顺序,运行时间是指算法完成执行所需的时间。
在算法设计中,有一些基础的数学原理非常重要,包括递归、迭代和分治等。
这些数学概念可以用来描述算法的递推式和复杂度,并可以为算法的优化提供指导。
1.递归递归是指在函数或子过程中调用自身。
递归常常可以简化算法的实现和代码的可读性。
递归的本质是将规模大的问题分解为规模较小的子问题,并通过递推求解出最终解。
递归需要考虑递归终止条件和递归式两个方面。
例如,计算斐波那契数列的第n项可以使用递归算法,其递推式为:f(n) = f(n-1) + f(n-2) (n >= 3) f(1) = 1, f(2) = 1通过递归式和递归终止条件可以求解斐波那契数列的任意项,例如:int fib(int n) { if (n == 1 || n == 2) return 1; return fib(n-1) + fib(n-2); }上述代码实现了斐波那契数列的递归算法,计算时间复杂度为O(2^n),因为对于每一项都需要递归计算。
为了提高效率,可以使用动态规划等方法。
2.迭代迭代是指在一定条件下重复执行同一操作,直到满足结束条件。
迭代的本质是通过循环求解出问题的最终解。
迭代需要考虑循环终止条件和迭代式两个方面。
例如,计算斐波那契数列的第n项可以使用迭代算法,其迭代式为:f[1] = f[2] = 1; for (int i = 3; i <= n; i++) { f[i] = f[i-1] + f[i-2]; }通过迭代式和循环终止条件可以求解斐波那契数列的任意项,例如:int fib(int n) { if (n == 1 || n == 2) return 1; int f[3] = {1, 1, 0}; for (int i = 3; i <= n; i++) { f[2] = f[0] + f[1]; f[0] = f[1]; f[1] = f[2]; } return f[2]; }上述代码实现了斐波那契数列的迭代算法,计算时间复杂度为O(n),因为只需要遍历一遍数组即可求解。
算法设计与分析复习题整理 (1)

一、基本题:算法:1、程序是算法用某种程序设计语言的具体实现。
2、算法就是一组有穷的序列(规则) ,它们规定了解决某一特定类型问题的一系列运算。
3、算法的复杂性是算法效率的度量,是评价算法优劣的重要依据。
4、算法的“确定性”指的是组成算法的每条指令是清晰的,无歧义的。
5、算法满足的性质:输入、输出、确定性、有限性。
6、衡量一个算法好坏的标准是时间复杂度低。
7、算法运行所需要的计算机资源的量,称为算法复杂性,主要包括时间复杂性和空间复杂性。
8、任何可用计算机求解的问题所需的时间都与其规模有关。
递归与分治:9、递归与分治算法应满足条件:最优子结构性质与子问题独立。
10、分治法的基本思想是首先将待求解问题分解成若干子问题。
11、边界条件与递归方程是递归函数的两个要素。
12、从分治法的一般设计模式可以看出,用它设计出的程序一般是递归算法。
13、将一个难以直接解决的大问题,分解成一些规模较小的相同问题,以便各个击破。
这属于分治法的解决方法。
14、Strassen矩阵乘法是利用分治策略实现的算法。
15、大整数乘积算法是用分治法来设计的。
16、二分搜索算法是利用分治策略实现的算法。
动态规划:17、动态规划算法的两个基本要素是最优子结构性质和重叠子问题性质。
18、下列算法中通常以自底向上的方式求解最优解的是动态规划法。
19、备忘录方法是动态规划算法的变形。
20、最优子结构性质是贪心算法与动态规划算法的共同点。
21、解决0/1背包问题可以使用动态规划、回溯法,其中不需要排序的是动态规划,需要排序的是回溯法。
贪心算法:22、贪心算法总是做出在当前看来最好的选择。
也就是说贪心算法并不从整体最优考虑,它所做出的选择只是在某种意义上的局部最优解。
23、最优子结构性质是贪心算法与动态规划算法的共同点。
24、背包问题的贪心算法所需的计算时间为 O(nlogn) 。
回溯法:25、回溯法中的解空间树结构通常有两种,分别是子集树和排列树。
算法设计与分析:递归与分治法-实验报告(总8页)

算法设计与分析:递归与分治法-实验报告(总8页)实验目的:掌握递归与分治法的基本思想和应用,学会设计和实现递归算法和分治算法,能够分析和评价算法的时间复杂度和空间复杂度。
实验内容:1.递归算法的设计与实现3.算法的时间复杂度和空间复杂度分析实验步骤:1)递归定义:一个函数或过程,在其定义或实现中,直接或间接地调用自身的方法,被成为递归。
递归算法是一种控制结构,它包含了解决问题的基础情境,也包含了递归处理的情境。
2)递归特点:递归算法具有以下特点:①依赖于递归问题的部分解被划分为若干较小的部分。
②问题的规模可以通过递推式递减,最终递归终止。
③当问题的规模足够小时,可以直接求解。
3)递归实现步骤:①确定函数的定义②确定递归终止条件③确定递归调用的过程4)经典实例:斐波那契数列递推式:f(n) = f(n-1) + f(n-2)int fib(int n) {if (n <= 0)return 0;else}5)优化递归算法:避免重复计算例如,上述斐波那契数列的递归算法会重复计算一些中间结果,影响效率。
可以使用动态规划技术,将算法改为非递归形式。
int f1 = 0, f2 = 1;for (int i = 2; i <= n; i++) {f1 = f2;使用循环避免递归,重复计算可以大大减少,提高效率。
1)分治算法的定义:将原问题分解成若干个规模较小且类似的子问题,递归求解子问题,然后合并各子问题得到原问题的解。
2)分治算法流程:②将问题分解成若干个规模较小的子问题。
③递归地解决各子问题。
④将各子问题的解合并成原问题的解。
3)分治算法实例:归并排序归并排序是一种基于分治思想的经典排序算法。
排序流程:②分别对各子数组递归进行归并排序。
③将已经排序好的各子数组合并成最终的排序结果。
实现源代码:void mergeSort(int* arr, int left, int right) {if (left >= right)while (i <= mid && j <= right)temp[k++] = arr[i] < arr[j] ? arr[i++] : arr[j++];temp[k++] = arr[i++];1) 时间复杂度的概念:指完成算法所需的计算次数或操作次数。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
这条特征涉及到分治法的效率,如果各子问题是不独 立的,则分治法要做许多不必要的工作,重复地解公 共的子问题,此时虽然也可用分治法,但一般用动态 规划较好。
分治法的求解过程
一般来说,分治法的求解过程由以下三个阶段组成:
(1)划分:既然是分治,当然需要把规模为n的原问题划分为k个规模较小的 子问题,并尽量使这k个子问题的规模大致相同。
}
思考题:给定a,用二分法设计出求an的算法。
例:计算an,应用分治技术得到如下计算方法:
a
n
n a
a 2 n ´a
34
2
如果 n 1 如果 n 1
32 31 31 3 9
32
分解问题 31 求解每个子问题 3
31
3 9
3
合并子问题的解
81
不是所有的分治法都比简单的蛮力法更有效。
第n个Fibonacci数可递归地计算如下: public static int fibonacci(int n) { if (n <= 1) return 1; return fibonacci(n-1)+fibonacci(n-2); }
边界条件
递归方程
6.1.1 递归算法的特性
三个特性
第六章 递归与分治法
递归
汉诺塔、斐波那契数列、八皇后问题
分治
二分法求方程近似解
6.1
递归
递归是一种在函数和方法中调用自身的编 程技术。 递归(Recursion)就是子程序(或函数) 直接调用自己或通过一系列调用语句间接 调用自己,是一种描述问题和解决问题的 基本方法。 递归有两个基本要素:
穷举法
在可能的解空间逐一尝试(8次比较) 分组(4次比较)
分治法
6.3.1 分治法概述
分治法所能解决的问题一般具有以下几个特征: 该问题的规模缩小到一定的程度就可以容易地解 决; 该问题可以分解为若干个规模较小的同构子问题, 即该问题具有最优子结构性质。 利用该问题分解出的子问题的解可以合并为该问 题的解; 该问题所分解出的各个子问题是相互独立的,即 子问题之间不包含公共的子问题。
1 Q 2 Q 3 Q 4 Q 5 Q 6 Q 7 Q 8 Q 1 2 3 4 5 6 7 8
6.3 分治法
分治法的设计思想是,将一个难以直接解 决的大问题,分割成一些规模较小的相同 问题,以便各个击破,分而治之。
凡治众如治寡,分数是也。
----孙子兵法
6.3.1 问题提出
16枚硬币,一枚伪造硬币,其重量要比真 硬币轻。任务:找出伪造硬币。
求解规模为n的问题可以转化为一个或多个结 构相同、规模较小的问题,然后从这些小问 题的解能方便的构造出大问题的解。 递归调用的次数必须是有限的。 必须有结束递归的条件(边界条件)来终止 递归。即当规模n=1时,能直接得解。
6.1.2 递归的执行过程
递推
把规模为n的问题的求解推到比原问题的规模 较小的问题求解,且必须要有终止递归的情 况。 当获得最简单情况的解后,逐级返回,依次 得到规模较大问题的解。
启发式规则:
1. 平衡子问题:最好使子问题的规模大致相同。也就是将一 个问题划分成大小相等的k个子问题(通常k=2),这种使子 问题规模大致相等的做法是出自一种平衡(Balancing)子问 题的思想,它几乎总是比子问题规模不等的做法要好。 2. 独立子问题:各子问题之间相互独立,这涉及到分治法的 效率,如果各子问题不是独立的,则分治法需要重复地解公 共的子问题。
6.4 分治法应用
快速排序、归并排序、二分查找 二分法求方程近似解
算法分析
将方程的有解空间平分为两个小区间,然后判断 解在哪个小区间; 继续把有解空间一分为二进行判断,如此周而复 始,直到求出满足精确要求的近似解。
步骤
二分搜索技术
给定已按升序排好序的n个元素a[0:n-1],现要在这n个元素中找 出一特定元素x。 分析: 该问题的规模缩小到一定的程度就可以容易地解决; 该问题可以分解为若干个规模较小的相同问题; 分解出的子问题的解可以合并为原问题的解; 分解出的各个子问题是相互独立的。 分析:如果n=1即只有一个元素,则只要比较这个元素和x就 分析:比较x和a的中间元素a[mid],若x=a[mid],则x在L中的 可以确定x是否在表中。因此这个问题满足分治法的第一个适 位置就是mid;如果x<a[mid],由于a是递增排序的,因此假 分析:很显然此问题分解出的子问题相互独立,即在a[i]的前 用条件 如x在a中的话,x必然排在a[mid]的前面,所以我们只要在 面或后面查找x是独立的子问题,因此满足分治法的第四个适 a[mid]的前面查找x即可;如果x>a[i],同理我们只要在a[mid] 用条件。 的后面查找x即可。无论是在前面还是后面查找x,其方法都 和在a中查找x一样,只不过是查找的规模缩小了。这就说明 了此问题满足分治法的第二个和第三个适用条件。
⑴ 边界条件:确定递归到何时终止; ⑵ 递归方程:大问题是如何分解为小问题的。
算法总体思想
对这k个子问题分别求解。如果子问题的规模仍然不 将要求解的较大规模的问题分割成k个更小规模的子 够小,则再划分为k个子问题,如此递归的进行下去, 问题。 直到问题规模足够小,很容易求出其解为止。
T(n)
=
n
回归
递归
优点
结构清晰、可读性强、容易证明算法的正确性
缺点
运行效率低
理论上,所有的递归算法都可以转换成非递归算 法
通过分析,跳过分解部分,直接用循环结构实现求值 过程。 用栈保存程序的运行过程。 利用栈保存参数。
Fibonacci数列非递归:
int Fibo(int n) { if(n==1||n==2)return 1 ; int a1=1,a2=1; int an ; for(int i=3;i<n;i++) { an=a1+a2; a1=a2; a2=an; } return an; }
递归方程 边界条件与递归方程是递归函数的二个要素,递归函 数只有具备了这两个要素,才能在有限次计算后得出 结果。
6.1
递归
例 Fibonacci数列
无穷数列1,1,2,3,5,8,13,21,34,55,…,被 称为Fibonacci数列。它可以递归地定义为:
1 n0 F ( n) 1 n 1 F (n 1) F (n 2) n 1
T(n)
n/2
=
n/2
n
n/2 n/2
T(n/4)T(n/4)T(n/4)T(n/4) T(n/4)T(n/4)T(n/4)T(n/4) T(n/4)T(n/4)T(n/4)T(n/4) T(n/4)T(n/4)T(n/4)T(n/4
6.1
例
递归
边界条件
阶乘函数
阶乘函数可递归地定义为:
n0 1 n! n(n 1)! n 0
T(n/2)
T(n/2)
T(n/2)
T(n/2)
算法总体思想
将求出的小规模的问题的解合并为一个更大规模的问 对这k个子问题分别求解。如果子问题的规模仍然不 题的解,自底向上逐步求出原来问题的解。 够小,则再划分为k个子问题,如此递归的进行下去, 直到问题规模足够小,很容易求出其解为止。
T(n)
n/2
规则1:每次只能移动1个圆盘; 规则2:任何时刻都不允许将较大的圆盘压在较小的圆盘之上; 规则3:在满足移动规则1和2的前提下,可将圆盘移至a,b,c中任 一塔座上。
n后问题
在n×n格的棋盘上放置彼此不受攻击的n个皇后。按照国际象 棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线 上的棋子。n后问题等价于在n×n格的棋盘上放置n个皇后, 任何2个皇后不放在同一行或同一列或同一斜线上。
(2)求解子问题:各子问题的解法与原问题的解法通常是相同的,可以用递 归的方法求解各个子问题,有时递归处理也可以用循环来实现。
(3)合并:把各个子问题的解合并起来,合并的代价因情况不同有很大差 异,分治算法的有效性很大程度上依赖于合并的实现。
divide-and-conquer(P) { if ( | P | <= n0) adhoc(P); //解决小规模的问题,直接求解 divide P into smaller subinstances P1,P2,...,Pk;//分解问题 for (i=1,i<=k,i++) yi=divide-and-conquer(Pi); //递归求解各子问题 return merge(y1,...,yk); //将各子问题的解合并为原问题的解 }
=
n/2
n
n/2 n/2
T(n/4)T(n/4)T(n/4)T(n/4) T(n/4)T(n/4)T(n/4)T(n/4) T(n/4)T(n/4)T(n/4)T(n/4) T(n/4)T(n/4)T(n/4)T(n/4
算法总体思想
将求出的小规模的问题的解合并为一个更大规模的问 题的解,自底向上逐步求出原来问题的解。[0:n-1],现要在这n个元素中找 出一特定元素x。 算法复杂度分析: 据此容易设计出二分搜索算法: 每执行一次算法的 public static int binarySearch(int [] a, int x, int n) while循环, 待搜索数 { 组的大小减少一半。因 // 在 a[0] <= a[1] <= ... <= a[n-1] 中搜索 x // 找到x时返回其在数组中的位置,否则返回-1 此,在最坏情况下, int left = 0; int right = n - 1; while循环被执行了 while (left <= right) { O(logn) 次。循环体内 int middle = (left + right)/2; 运算需要O(1) 时间, if (x == a[middle]) return middle; if (x > a[middle]) left = middle + 1; 因此整个算法在最坏情 else right = middle - 1; 况下的计算时间复杂性 } 为O(logn) 。 return -1; // 未找到x