我接触acm将近一年了。总结起来,就是:刷题,囤积基础编程能力的技巧;刷题&看些简单PPT或者听老队员讲讲算法啥的,也是囤积基础能力;拼命刷题,将自己不会的暴露出来,将自己会的熟练起来;读论文,有了前面的基础,大牛教主写的论文也就不会显得那么难以攻破了,也能学到一些以前未曾想过的算法的运用;
总之经过这些训练,算法能力会得到很好的锻炼。在以前的教科书里,算法是计算机学科的核心。虽然现在的观念淡化了算法的重要性,更多地注重软件的架构和利益等方面,但是基本的算法学习还是必不可少的。扎实的算法和数学功底是计算机研究的必备条件,而这些正是ACM所培养的。
根据课程学习的内容,学习总结分为四个方面:
1. 贪心算法总结
贪心与其说是一种算法,更是一种思想。在构造最优解的每一步过程中,都在构造局部最优解,解这类问题关键是找到最优策略,并分析最优子结构能否构造全局最优解。
1.1 贪心策略的定义
【定义1】 贪心策略是指从问题的初始状态出发,通过若干次的贪心选择而得出最优值(或较优解)的一种解题方法。
其实,从"贪心策略"一词我们便可以看出,贪心策略总是做出在当前看来是最优的选择,也就是说贪心策略并不是从整体上加以考虑,它所做出的选择只是在某种意义上的局部最优解,而许多问题自身的特性决定了该题运用贪心策略可以得到最优解或较优解。在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的仅是在某种意义上的局部最优解。贪心算法不是对所有问题都能得到整体最优解,但对范围相当广泛的许多问题他能产生整体最优解或者是整体最优解的近似解。
1.2 贪心算法的特点
通过上文的介绍,可能有人会问:贪心算法有什么样的特点呢?我认为,适用于贪心算法解决的问题应具有以下2个特点:
1.21 贪心选择性质:
所谓贪心选择性质是指应用同一规则f,将原问题变为一个相似的、但规模更小的子问题、而后的每一步都是当前看似最佳的选择。这种选择依赖于已做出的选择,但不依赖于未做出的选择。从全局来看,运用贪心策略解决的问题在程序的运行过程中无回溯过程。关于贪心选择性质,读者可在后文给出的贪心策略状态空间图中得到深刻地体会。贪心算法是一种改进了的分级处理方法。其核心是根据题意选取一种量度标准。然后将这多个输入排成这种量度标准所要求的顺序,按这种顺序一次输入一个量。如果这个输入和当前已构成在这种量度意义下的部分最佳解加在一起不能产生一个可行解,则不把此输入加到这部分解中。
1.22 局部最优解:
我们通过特点2向大家介绍了贪心策略的数学描述。由于运用贪心策略解题在每一次都取得了最优解,但能够保证局部最优解得不一定是贪心算法。如大家所熟悉得动态规划算法就可以满足局部最优解,在广度优先搜索(BFS)中的解题过程亦可以满足局部最优解。
在遇到具体问题时,往往分不清哪些题该用贪心策略求解,哪些题该用动态规划法求解。在此,我们对两种解题策略进行比较。
【引例】其中中的经典题:钓鱼问题(枚举+贪心)
把每钓5分钟鱼称为钓一次鱼。首先枚举John需要走过的池塘的数目X,即从池塘1走到池塘X。减去路上花去的时间T=sum(Ti) i=1...X-1,这样我们可以认为John能从一个池塘"瞬间转移"到另一个池塘,即在任意一个时刻都可以在池塘1到池塘X中任选一个钓一次鱼(很重要)。现在采用贪心策略,每次选择鱼最多的池塘钓一次鱼。对于每个池塘来说,由于在任何时候鱼的数目只和John在该池塘里钓鱼的次数有关,和钓鱼的总次数无关,所以这个策略是最优的。假设一共允许钓k次鱼,那么每次在N个池塘中选择鱼最多的一个钓。总的时间复杂度为O(kn^2)。(黑书中的解释)
在最后的结果中,第一个最大值所对应的在每个池塘的钓鱼时间为题目要求的情况,因为如果John在后面的池塘钓了鱼,那么前面相应的时间就会减少。最后注意池塘中没有鱼的情况。
结题步骤如下:
1.在枚举了走过的池塘数目下,分别计算(总时间s-花在路上的时间t)/5=times就是钓鱼的次数。
2.首先选取当前池塘鱼数最多的池塘,然后可掉鱼数max+鱼数,然后次数减一,当池塘中的鱼<0时赋值为0,并退出本次枚举,或者次数为0时,退出本次枚举,然后记录鱼数。如果还有次数,但是没有鱼了,把剩余次数加在第一个池塘。
总之,贪心算法可以说是求最优解的一个捷径。
2. 搜索算法总结
2.1深度优先搜索
2.11深度优先搜索算法概念及性质
深度优先搜索所遵循的搜索策略是尽可能“深”地搜索图。
在深度优先搜索中,对于最新发现的节点,如果它还有以此为起点而未搜索的边,就沿此边继续搜索下去。当节点v的所有边都己被探寻过,搜索将回溯到发现节点v有那条边的始节点。这一过程一直进行到已发现从源节点可达的所有节点为止。如果还存在未被发现的节点,则选择其中一个作为源节点并重复以上过程,整个进程反复进行直到所有节点都被发现为止。即
⒈以给定的某个顶点V0为起始点,访问该顶点;
⒉选取一个与顶点V0相邻接且未被访问过的顶点V1,用V1作为新的起始点,重复上述过程;
⒊当到达一个其所有邻接的顶点都已被访问过的顶点Vi时,就退回到新近被访问过的顶点Vi- 1,继续访问Vi-1尚未访问的邻接点,重复上述搜索过程;
⒋直到从任意一个已访问过的顶点出发,再也找不到未被访问过的顶点为止,遍历便告完成。
这种搜索的次序体现了向纵深发展的趋势,所以称之为深度优先搜索。
2.12深度优先搜索算法描述
程序实现有两种方式--递归与非递归。
一、递归
递归过程为:
Procedure DEF-GO(step)
for i:=1 to max do
if 子结点符合条件 then
产生新的子结点入栈;
if 子结点是目标结点 then 输出
else DEF-GO(step+1);
栈顶结点出栈;
endif;
enddo;
主程序为: Program DFS;
初始状态入栈;
DEF-GO(1);
二、非递归
Program DEF(step);
step:=0;
repeat
step:=step+1;
j:=0;
p:=false repeat
j:=j+1;
if 结点符合条件 then 产生子结点入栈;
if 子结点是目标结点 then 输出 else p:=true;
else
if j>=max then 回溯 p:=false;
endif;
until p=true;
until step=0;
回溯过程如下:
Procedure BACK;
step:=step-1;
f step>0 then 栈顶结点出栈 elsep:=true;
两种方式本质上是等价,但两者也时有区别的。递归方式实现简单,非递归方式较之比较复杂;
递归方式需要利用栈空间,如果搜索量过大的话,可能造成栈溢出,所以在栈空间无法满足的情况下,选用非递归实现方式较好。
2.2广度优先搜索遍历算法
2.21 广度优先搜索的概念及性质
广度优先搜索算法(又称宽度优先搜索)是最简便的图的搜索算法之一,这一算法也是很多重要的图的算法的原型。Dijkstra单源最短路径算法和Prim最小生成树算法都采用了和宽度优先搜索类似的思想。
广度优先算法的核心思想是:从初始节点开始,应用算符生成第一层节点,检查目标节点是否在这些后继节点中,若没有,再用产生式规则将所有第一层的节点逐一扩展,得到第二层节点,并逐一检查第二层节点中是否包含目标节点。若没有,再用算符逐一扩展第二层的所有节点 ,如此依次扩展,检查下去,直到发现目标节点为止。即
⒈从图中的某一顶点V0开始,先访问V0;
⒉访问所有与V0相邻接的顶点V1,V2,......,Vt;
⒊依次访问与V1,V2,......,Vt相邻接的所有未曾访问过的顶点; ⒋循此以往,直至所有的顶点都被访问过为止。
这种搜索的次序体现沿层次向横向扩长的趋势,所以称之为广度优先搜索。
2.22 广度优先搜索算法描述:
Program Bfs;
初始化,初始状态存入OPEN表;
队列首指针head:=0;尾指针tail:=1;
repeat
指针head后移一位,指向待扩展结点;
for I=1 to max do {max为产生子结点的规则数} begin
if 子结点符合条件 then begin
tail指针增1,把新结点存入列尾;
if新结点与原已产生结点重复then删去该结点(取消入队,tail减1) else
if新结点是目标结点then输出并退出; end;end;
until(tail>=head); {队列空}
2.23广度优先搜索注意事项:
1、每生成一个子结点,就要提供指向它们父亲结点的指针。当解出现时候,通过逆向跟踪,找到从根结点到目标结点的一条路径。
2、生成的结点要与前面所有已经产生结点比较,以免出现重复结点,浪费时间,还有可能陷入死循环。
3、如果目标结点的深度与“费用”(如:路径长度)成正比,那么,找到的第一个解即为最优解,这时,搜索速度比深度搜索要快些;如果结点的“费用”不与深度成正比时,第一次找到的解不一定是最优解。
4、广度优先搜索的效率还有赖于目标结点所在位置情况,如果目标结点深度处于较深层时,需搜索的结点数基本上以指数增长。
2.3 深度优先算法与广度优先算法的区别
通常深度优先搜索法不全部保留结点,扩展完的结点从数据库中弹出删去,这样,一般在数据库中存储的结点数就是深度值,因此它占用空间较少。所以,当搜索树的结点较多,用其它方法易产生内存溢出时,深度优先搜索不失为一种有效的求解方法。
广度优先搜索算法,一般需存储产生的所有结点,占用的存储空间要比深度优先搜索大得多,因此,程序设计中,必须考虑溢出和节省内存空间的问题。但广度优先搜索法一般无回溯操作,即入栈和出栈的操作,所以运行速度比深度优先搜索要快些。
3. 动态规划总结
3.1什么是动态规划(DP)
非常重要!!不要认为概念不重要,理解的深刻,你才知道对于什么样的问题去考虑有没有动态规划的方法,以及如何去使用动态规划。
1)动态规划是运筹学中用于求解决策过程中的最优化数学方法。当然,我们在这里关注的是作为一种算法设计技术,作为一种多阶段决策过程最优的通用方法。
他是应用数学中用于解决某类最优化问题的重要工具。
2)如果问题是由交叠的子问题所构成,我们就可以用动态规划技术来解决它,一般来说,这样的子问题出现在对给定问题求解的递推关系中,这个递推关系包含了相同问题的更小子问题的解。动态规划法建议,与其对交叠子问题一次又一次的求解,不如把每个较小子问题只求解一次并把结果记录在表中(动态规划也是空间换时间的),这样就可以从表中得到原始问题的解。
算法思想:
动态规划算法的思想和分治法是一样的,即将待解决的问题分解成若干个子问题,找到字问题的解,再去构造源问题的解。
3.11 与分治法的异同
同:算法思想一样,都是对问题进行递归分解。
异:
1 当分解得到的子问题不相互独立时(我觉得是A问题的解要用到B,C问题的解,但B,C问题的解已经算出
了,但是因为没有保存,所以A还要再计算一遍,所以重复计算了,所以用分治法则浪费不必要的计算时
间),此时适合用动态规划的算法解决,因为动态规划算法对求解出来的字问题的结果有保存,用到的
时候只需要花常数的时间去查表就行了,从而减少了重复的计算量。
2 动态规划是用自底向上的方式计算,分治法用的是自顶向下的计算方式,递归也是自顶向下方式的。
3.12与备忘录算法的异同
1 备忘录是自顶向下,动态规划是自底向上,递归是自顶向下的计算方式。
2 动态规划每个子问题都要解一次,但不会求解重复子问题;备忘录方法只解哪些确实
需要解的子问题;递归方法每个子问题都要解一次,包括重复子问题• 。
3 当一个问题的所有子问题都至少要解一次时,用动态规划算法较好。当子问题空间中
的部分子问题不需要求解时,用备忘录方法则较好。
3.2动态规划算法适用条件
1 最优子结构性质(反证法)
当问题的最优解包含着子问题的最优解时,称该问题具有最优子结构性质。
2 重叠子问题性质
递归求解问题时,每次求解子问题并不总是新问题,有些子问题被反复计算多次。这种性质称为子问题
的重叠性质。
3 无后效性
将各阶段按照一定的次序排列好之后,对于某个给定的阶段状态,它以前各阶段的状态无法直接影响它未来
的决策,而只能通过当前的这个状态。换句话说,每个状态都是过去历史的一个完整总结。这就是无后向
性,又称为无后效性。
3.3动态规划算法基本步骤
1 将原问题分解为多个子问题。
2 找到子问题的解。(最优解)
3 用子问题的解构造原问题的解。
对于第三步有两种方法实现:
自顶向下(Top-down):采用递归形式实现。使用该方法需要采用额外变量来存储已经解决的子问题结果,来避免递归产生的重复计算问题。天然具有获得产生最优解步骤的能力。
自底向上(Bottom-up):采用迭代形式,将所有计算问题可能用到子问题的解都计算出来,然后用他们构造源 问题的解。使用该方法需要用额外变量来存储产生最优解的步骤。天然具有消除重复计算的能力。
结语
总之,在一个学期的学习中,特别是这一个实践活动周,让我对程序设计有了更大的兴趣。在这中间让我学习到了许多有用的东西。在以前我从不知道ACM,在这里我知道了它,也了解了它。
1,语言是最重要的基本功。
2,以数学为主的基础知识最重要。
3,团队合作很重要。
4,练习,练习,再练习。
这四点是学习ACM的基础,我感觉也是学习开发语言的基础。也知道做任何事情无论是对是错,都必须好好思考总结一番,如果我做错了,我应该吸取教训,改正错误;如果我是对的,那我应该再接再厉,取得更大的进步。前面的四点是ACM的基础,是大家都知道的。而我也有自己的一些心得,学习ACM的好处。我想有以下几个方面的进步是可以肯定的:第一是可以提高对编程语言的熟悉程度,对编程语言越熟悉越能编写出高效的代码;第二可以提高编写代码的能力,你可以一次写多少行的代码并且保证其正确性?你可以在一个小时中写出多少行的代码?如果你有意识的训练自己的话,还可以养成好的编程习惯和代码风格;第三点是对数据结构与算法的掌握,这个是最重要的一条。虽然我没有很好的掌握,但是我会努力。
以前的我写一些什么代码的,从没有感觉到写一个代码还要去考虑多少东西,就知道的是,想到了什么,结果出来了吗?到现在我才知道,原来那是一个刚刚学走路的小孩,什么东西都不懂。还有执行的时间,占用的内存......这些都是一个好的程序员要考虑的,这些也是我从这个课上学习到的,这也是我的一个重要的体会。这些都没有把我吓到,值得庆幸的是,我还喜欢上了一点这个。
练习,练习,再练习。努力,努力,再努力!不变的学习方法。