算法分析与设计课程总结
经过8周的学习,我对算法有了更深入的理解。代码水平也有了显著的提高。
我们学习的算法有:递归与分治策略,贪心算法,回溯算法,分支限界算法和动态规划算法。
一、递归与分治策略
(一)递归
程序调用自身的编程技巧称为递归。一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。递归的能力在于用有限的语句来定义对象的无限集合。一般来说,递归需要有边界条件、递归前进段和递归返回段。当边界条件不满足时,递归前进;当边界条件满足时,递归返回。
注意:
(1) 递归就是在过程或函数里调用自身;
(2) 在使用递归策略时,必须有一个明确的递归结束条件,称为递归出口。
分治法是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。
(二)分治法
分治法所能解决的问题一般具有以下几个特征:
(1) 该问题的规模缩小到一定的程度就可以容易地解决;
(2) 该问题可以分解为若干个规模较小的相同问题,即该问题具有最优子结构性质;
(3) 利用该问题分解出的子问题的解可以合并为该问题的解;
(4) 该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子子问题。
(三)实例分析
给定一个二叉树,找出其最大深度。
二叉树的深度为根节点到最远叶子节点的距离。
样例
给出一棵如下的二叉树:
这个二叉树的最大深度为3。
这道题就是利用递归的思想,从根节点开始不断的递归,每向下递归一次深度加一,遇到叶子节点就返回当前深度,记录最大值即可。
二、贪心算法
贪心是一种思想,他不仅仅是 按照某种规则排序,然后一重 for 循环,按照某种标准找出最优解 这个套路。包含的还应该有更多。
比如搬桌子的那个题,“贪心”的做法应该是,按照搬桌子的终点从小到大排序,然后和安排活动一样,一重for判断是否可以插入,然后等到全部检索完毕后退出大循环。这两种方法从复杂度来看,区别的确不算大,两个都需要for循环很多次,但是,课件上的方法显然可以在较短的时间内调出代码,并且简单粗暴,方便理解。
贪心的思想是把大问题拆分为同类或者极其相似的更加简单求局部最优解的问题,然后最终求下来就可能是整体的最优解。费老师说过,贪心算法并不适合对所有的问题求最优解,有的时候最后求出来的不一定是符合题目的最优解。就好像小船的问题吧。如果贪心最短时间的话,就很难保证最短次数。像这种同时有两个标准的题目,可能用贪心就不是那么简单,如果仔细考虑整个大问题的话,是可以从中分拆出简单的思路的。
贪心算法是一种对某些求最优解问题的更简单、更迅速的设计技术。
用贪心法设计算法的特点是一步一步地进行,常以当前情况为基础根据某个优化测度作最优选择,而不考虑各种可能的整体情况,它省去了为找最优解要穷尽所有可能而必须耗费的大量时间,它采用自顶向下,以迭代的方法做出相继的贪心选择,每做一次贪心选择就将所求问题简化为一个规模更小的子问题, 通过每一步贪心选择,可得到问题的一个最优解,虽然每一步上都要保证能获得局部最优解,但由此产生的全局解有时不一定是最优的,所以贪婪法不要回溯。
(一)实例分析
FatMouse prepared M pounds of cat food, ready to trade with the cats guarding the warehouse containing his favorite food, JavaBean.
The warehouse has N rooms. The i-th room contains J[i] pounds of JavaBeans and requires F[i] pounds of cat food. FatMouse does not have to trade for all the JavaBeans in the room, instead, he may get J[i]* a% pounds of JavaBeans if he pays F[i]* a% pounds of cat food. Here a is a real number. Now he is assigning this homework to you: tell him the maximum amount of JavaBeans he can obtain.
Input
The input consists of multiple test cases. Each test case begins with a line containing two non-negative integers M and N. Then N lines follow, each contains two non-negative integers J[i] and F[i] respectively. The last test case is followed by two -1’s. All integers are not greater than 1000.
Output
For each test case, print in a single line a real number accurate up to 3 decimal places, which is the maximum amount of JavaBeans that FatMouse can obtain.
Sample Input
5 3
7 2
4 3
5 2
20 3
25 18
24 15
15 10
-1 -1
Sample Output
13.333
31.500
老鼠用猫食物换取自己最喜爱的食物javaBean,当然换取的最终结果是保证最后的JavaBean是最多的,而且是当自己手中的猫食物小于每个仓库所需交换的猫食物时候,可以手中有多少就交换多少。所以在解这道题时候要想到按照每个仓库javaBean最大的比率排序才能保证最后的交换的javaBean是最大的。
本题的贪心策略是按照性价比进行贪心,将每种方案的性价比求出来,按照性价比排序,然后优先交换性价比高的。
三、回溯算法
回溯算法也叫试探法,它是一种系统地搜索问题的解的方法。回溯算法的基本思想是:从一条路往前走,能进则进,不能进则退回来,换一条路再试。
回溯法(探索与回溯法)是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。
其基本思想是,在包含问题的所有解的解空间树中,按照深度优先搜索的策略,从根结点出发深度探索解空间树。当探索到某一结点时,要先判断该结点是否包含问题的解,如果包含,就从该结点出发继续探索下去,如果该结点不包含问题的解,则逐层向其祖先结点回溯。(其实回溯法就是对隐式图的深度优先搜索算法)。 若用回溯法求问题的所有解时,要回溯到根,且根结点的所有可行的子树都要已被搜索遍才结束。 而若使用回溯法求任一个解时,只要搜索到问题的一个解就可以结束。
在现实中,有很多问题往往需要我们把其所有可能穷举出来,然后从中找出满足某种要求的可能或最优的情况,从而得到整个问题的解。回溯算法就是解决这种问题的“通用算法”,有“万能算法”之称。
(一)实例分析:
N皇后问题在N增大时就是这样一个解空间很大的问题,所以比较适合用这种方法求解。这也是N皇后问题的传统解法,很经典。
求解N皇后问题是算法中回溯法应用的一个经典案例。问题如下:
在N*N的方格棋盘放置了N个皇后,使得它们不相互攻击(即任意2个皇后不允许处在同一排,同一列,也不允许处在与棋盘边框成45角的斜线上。你的任务是,对于给定的N,求出有多少种合法的放置方法。
解法如下:
在具体解决该问题时,可以将其拆分为几个小问题。首先就是在棋盘上如何判断两个皇后是否能够相互攻击,在最初接触这个问题时,首先想到的方法就是把棋盘存储为一个二维数组,然后在需要在第i行第j列放置皇后时,根据问题的描述,首先判断是在第i行是否有皇后,由于每行只有一个皇后,这个判断也可以省略,然后判断第j列是否有皇后,这个也很简单,最后需要判断在同一斜线上是否有皇后,按照该方法需要判断两次,正对角线方向和负对角线方向
1) 算法开始, 清空棋盘,当前行设为第一行,当前列设为第一列
2) 在当前行,当前列的位置上判断是否满足条件(即保证经过这一点的行,列与斜线上都没有两个皇后),若不满足,跳到第4步
3) 在当前位置上满足条件的情形:
在当前位置放一个皇后,若当前行是最后一行,记录一个解;
若当前行不是最后一行,当前行设为下一行, 当前列设为当前行的第一个待测位置;
若当前行是最后一行,当前列不是最后一列,当前列设为下一列;
若当前行是最后一行,当前列是最后一列,回溯,即清空当前行及以下各行的棋盘,然后,当前行设为上一行,当前列设为当前行的下一个待测位置;
以上返回到第2步
4) 在当前位置上不满足条件的情形:
若当前列不是最后一列,当前列设为下一列,返回到第2步;
若当前列是最后一列了,回溯,即,若当前行已经是第一行了,算法退出,否则,清空当前行及以下各行的棋盘,然后,当前行设为上一行,当前列设为当前行的下一个待测位置,返回到第2步;
四、分支限界算法
分支定界法的基本思想是对有约束条件的最优化问题的所有可行解(数目有限)空间进行搜索。该算法在具体执行时,把全部可行的解空间不断分割为越来越小的子集(称为分支),并为每个子集内的解的值计算一个下界或上界(称为定界)。在每次分支后,对凡是界限超出已知可行解值那些子集不再做进一步分支,这样,解的许多子集(即搜索树上的许多结点)就可以不予考虑了,从而缩小了搜索范围。这一过程一直进行到找出可行解为止,该可行解的值不大于任何子集的界限。因此这种算法一般可以求得最优解。
与贪心算法一样,这种方法也是用来为组合优化问题设计求解算法的,所不同的是它在问题的整个可能解空间搜索,所设计出来的算法虽其时间复杂度比贪婪算法高,但它的优点是与穷举法类似,都能保证求出问题的最佳解,而且这种方法不是盲目的穷举搜索,而是在搜索过程中通过限界,可以中途停止对某些不可能得到最优解的子空间进一步搜索(类似于人工智能中的剪枝),故它比穷举法效率更高。
五、动态规划
动态规划基本思想是,将原问题分解为相似的子问题,在求解的过程中通过子问题的解求出原问题的解。动态规划程序设计是对解最优化问题的一种途径、一种方法,而不是一种特殊算法。动态规划程序设计往往是针对一种最优化问题,由于各种问题的性质不同,确定最优解的条件也互不相同,因而动态规划的设计方法对不同的问题,有各具特色的解题方法,而不存在一种万能的动态规划算法,可以解决各类最优化问题。
(一)实例分析:
在讲述DP算法的时候,一个经典的例子就是数塔问题,它是这样描述的:
有如下所示的数塔,要求从顶层走到底层,若每一步只能走到相邻的结点,则经过的结点的数字之和最大是多少?
Input
输入数据首先包括一个整数C,表示测试实例的个数,每个测试实例的第一行是一个整数N(1 <= N <= 100),表示数塔的高度,接下来用N行数字表示数塔,其中第i行有个i个整数,且所有的整数均在区间[0,99]内。
Output
对于每个测试实例,输出可能得到的最大和,每个实例的输出占一行。
Sample Input
1
5
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
Sample Output
30
这是动态规划中一个经典的模型:三角模型。
借用之前看到的一个资料,形象化一下动态规划。
从前,有一个国王,然后呢,他想从顶部开始往下走,路就是上题中的图。 那么,他要怎么走才能让进过的和最大呢? 他可以找一个大臣,先去谈谈第二行的路,让大臣告诉他走左边的路最后得到的和比较大还是走右边的路得到的和比较大。 那么这个大臣怎么知道第二行的路那条最大呢?那么他就可以找到他的小伙计,让他的小伙计去第三行探探路,然后让他的小伙计告诉他第二行那条路往第三行走得到的和比较大,并且告诉他最优的第三行的选择。以此类推,那么到了最后一个人的时候,他就不用纠结了对吧,因为不需要往下走了,最大的是哪个,他很清楚,然后他就汇报给他的上一级,然后他的上级再汇报给他的上级,最后汇报到国王那里。 这样,国王就知道了走哪条路是和最大的。
动态转移方程如下:dp [ i ] [ j ] + = max ( dp [ i + 1 ] [ j ] , dp [ i + 1 ] [ j + 1 ] ) i 和 j 表示 i 行 j 列 。
六、小结
算法设计与分析是数据结构的有力补充,从中可以了解到算法设计的奥妙以及对数据结构中的数据存储结构更深层次的运用。计算机算法设计与分析是面向设计的、处于核心地位的一门学科。算法是一组有穷的规则,它规定了解决某一特定类型问题的一系列运算。
在本学期的学习中,我学习到了很多关于算法的知识,对算法有了更深入的理解。运用这些知识可以利用计算机解决一些繁琐问题。在我今后的学习中,起到举足轻重的作用。