ACM课程总结

ACM程序设计总结

     在大一下学期的时候,老师就开了ACM程序设计这门课,当时也跟着老师上了几节课。一开始还是挺有兴趣的,感觉可以学到很多知识,但是学了几节课之后发现自己有好多知识都不知道,完全跟不上老师的思路,虽然C++在那个时候已经学习的差不多了,但是自己能够真正掌握的还是很少的。而且自己在上大学之前对程序设计语言一点都不了解,又不像男生头脑灵活,所以学习起来很吃力。直到大二又有了选择这门课的机会,通过一个学期的学习,我确实学到了不少知识。并不总是像一个机器一样敲代码,而是具备了解决某些问题的能力。本学期老师总共开了四个专题:一、贪心算法;二、搜索;三、动态规划;四、图论。每个专题老师都会在HDU上面布置一些题目,来让我们熟练一下学习的知识,另外把代码写上还不够,老师让我们每个题目写完之后就总结一下这个题目,写一篇博客,上面写上你的解题思路形成过程和你的感悟等,因为老师知道现在的科技发达,许多信息都是共享的,代码也不例外。在网上随便一搜就可以有好多重题,然后就能够找到你所需要的代码,老师的做法刚好就减少了许多水分,而且也可以让我们重新整理自己的思路,回顾自己所写的代码,加深印象。我认为老师的这个做法很好。接下来,我就总结一下我所学过的每个专题。

     第一个专题是贪心算法,就是在求解最优解的时候,依据某种贪心标准,从问题的最初状态出发,直接去求每一步的最优解,通过若干次的贪心选择,最终得出整个问题的最优解。从定义就可以看出来贪心算法所做出的选择只是在某种意义上的局部最优解,但不能保证总是有效的,因为它不是对所有的问题都能得到整体的最优解。所以在选择利用贪心算法的时候还是需要考虑两个问题:1.该题是否适合使用贪心算法求解;2.如何选择贪心标准来得到问题最优或者是较优解。如果它可以用贪心算法来求解,那么它的求解过程还要考虑:候选集合、解集合、解决函数、选择函数和可行函数。候选集合是问题的输入集合,在初始的时候,解集合为空集,在候选集合中做出贪心选择,然后判断在解集合中加入解后是否可行。关于贪心算法的经典例子有很多,比如:最优装载问题、删数问题、多处最优服务次序问题等等。我印象最深的就是背包问题,因为开始的时候只知道物品的重量和价值,不知道用什么来顺序选取物品,老师也让我们思考,有一些人说用性价比,我不得不佩服他们的睿智,这个关键问题就迎刃而解了,接下来就可以用贪心算法啦~

第二个要总结的就是搜索,搜索又分为广度优先搜索(BFS)和深度优先搜索(DFS)。广度搜索的思想是从最初状态开始,利用规则生成所有可能的状态构成下一层结点,检查是否出现了目标状态,如果没有出现就对该层所有的状态结点分别顺序利用规律再生成下一层结点,然后在这一层结点检查是否出现了目标状态,若还是没有则一层一层地往下展开,知道目标状态找到为止。而队列的先进先出性质正好符合广度优先搜索,也就是说每次可以取队首元素进行拓展,然后把所有得到的扩展元素的可行状态都放在队列里面,没有该队首元素的扩展元素则将初始状态(队首元素)删除,然后一直重复前面的那些步骤。深度优先搜索的思想是从初始状态开始,利用规则搜索树下一层任一个结点,检查是否是目标状态,如果不是那么就继续利用规则搜索树的下一层的任意结点,然后再检查,直到不能生成新的状态结点(叶子结点),当它仍然还不是目标状态时,就回溯到上一层的结果,取另一个可能扩展搜索的分支,采用同样的办法一直进行下去直到找到目标状态为止。很显然,栈的先进后出的性质正好符合深度优先搜索,也就是先取一个栈顶元素,对他进行扩展,如果这个元素没有可以扩展的对象就将他从栈中弹出,继续上一步,不断地重复上述步骤直到找到目标状态或者栈空没有解。当然了,这两种搜索都有固定的搜索框架,而且深度优先搜索还有递归和非递归框架。老师给我们的固定算法如下:

广度优先搜索:

While Not Queue.Empty ()

Begin

可加结束条件

Tmp = Queue.Top ()

从Tmp循环拓展下一个状态Next

If 状态Next合法 Then

Begin

生成新状态Next

Next.Step = Tmp.Step + 1

Queue.Pushback (Next)

End

Queue.Pop ()

End

深度优先搜索的递归:

Function Dfs (Int Step, 当前状态)

Begin

可加结束条件

从当前状态循环拓展下一个状态Next

If 状态Next合法 Then

Dfs (Step + 1, Next ))

End

深度优先搜索的非递归:

While Not Stack.Empty ()

Begin

Tmp =Stack.top()

从Tmp拓展下一个未拓展的状态Next

If 没有未拓展状态(到达叶节点) Then

Stack.pop()

ElseIf 状态Next合法 Then

Stack.push(Next)

End

套用以上算法,可以更加简单地解决问题,当然了,自己应该必须懂得每一步算法的意思,才能更加快速地做题。

    第三个问题是动态规划问题,它是解决多阶段决策问题的一种方法,就是一种排除重复计算的算法,可以说是空间换时间。老师说前面用贪心算法的问题一般也可以用动态规划来解决,其中多阶段决策问题就是如果一类问题的求解过程可以分为若干个互相联系的阶段,且在每一个阶段都需要做出决策并影响到下一个阶段的决策。在辅修过程中,我学习了管理运筹学这门课,上面也讲到了动态规划,不光如此,离散和数据结构上面都有讲过,看来动态规划挺重要的。在其他课上的动态规划定义是:动态规划是一种研究多阶段决策问题的理论和方法,在决策过程中将问题分为若干阶段,对不同的阶段采取不同的决策,使全过程达到整体最优。动态规划问题具有以下基本特征:1.问题具有多阶段决策的特征;2.每一个阶段都有相应的“状态”与之对应,描述状态的量称为“状态变量”;3.每个阶段都面临一个决策,选择不同的决策将会导致下一个阶段不同的状态;4.每一个阶段的最优解可以递归地归结为下一个阶段各个可能状态的最优解各子问题和原问题具有完全相同的结构。其中所说的阶段是根据空间顺序或者是时间顺序对问题的求解划分阶段,状态是描述事物的性质,决策是根据题意要求,对每个阶段做出的某种选择性操作,状态转移方程是用数学公式描述与阶段相关的状态间的演变规律。知道这些是学习动态规划最基本的,最核心的是动态规划问题一般步骤:1.判断问题是否具有最优子结构性质,如果不具备就不能使用动态规划;2.把问题划分为若干个子问题;3.建立状态转移方程;4.找到边界条件;5.将已知边界值带入方程;6.递推求解。经典的动态规划问题就是楼梯问题、最长上升子序列、最大子段和问题。刚开始做题目的时候,我觉得非常有意思,就是找问题中的状态转移方程,每次通过推算找到状态转移方程我就激动好几天,那种感觉真的特别好,在做题的过程中,我发现其实在大一的时候我们就已经接触了动态规划问题,比如那个蜜蜂的问题,这个问题是我们在C++考试的时候出的问题,当时不理解是怎么回事,只是套用老师上课的时候给我们讲过的一些求解方法,其实那个求解方法就是状态转移方程。

    最后一个专题是图论,为了让我们更好地理解图之间的逻辑关系,老师给我们讲了图的存储表示——邻接表、与图论相关的算法广度优先搜索和深度优先搜索,最重要的两个问题是最小生成树和最短路径问题。首先是最小生成树,讲了关于求它的两个算法Prim算法和Kruskal算法。其中Prim算法就是任取一个顶点加入到生成树,在那些一个端点在生成树里,另一个端点不在生成树里的边中选取一个权值最小的边将它和另一个端点加入生成树,重复上面的步骤直到所有的顶点都加入到生成树为止。具体做法是:1.任意选定一点S,设集合S={s};2.从不在集合S的点中选出一个点就、使其与S内的某点i的距离最短,则(i,j)就是生成树上的一条边,同时将j点加入S;3.转到到2继续进行,直至所有点都已加如S集合。Kruskal算法就是将边按照权值从小到大排序后逐个判断,如果当前的边加入以后不会产生环,那么就把当前边作为生成树的一条边,最终得到的结果就是最小生成树,并查集。对于稀疏图来说,用Kruskal写最小生成树效率更好,加上并查集,可对其进行优化。我们可以把每个连通分量看成一个集合,该集合包含了连通分量的所有点。而具体的连通方式无关紧要,好比集合中的元素没有先后顺序之分,只有“属于”与“不属于”的区别。图的所有连通分量可以用若干个不相交集合来表示。而并查集的精妙之处在于用数来表示集合。如果把x的父结点保存在p[x]中(如果没有父亲,p[x]=x),则不难写出结点x所在树的递归程序:

Find(intx){

returnP[x]=x?x:P[x]=Find(P[x]

意思是,如果p[x]=x,说明x本身就是树根,因此返回x;否则返回x的父亲p[x]所在树的根结点。另外一个问题是最短路问题,在网图中,最短路径是指两个顶点之间经历的边上权值之和最短的路径。对于这个问题老师给出了很多算法,其中单源最短路径用Bellman—Ford算法、spfa算法、dijstral算法;每对顶点的最短路径用floyd—washall算法。单源最短路径的算法首先讲的是迪杰斯特拉算法——按路径长度递增的次序产生最短路径的算法。它的基本思想是:设置一个集合S存放已经找到最短路径的顶点,S的初始状态只包含源点v,对vi∈V-S,假设从源点v到vi的有向边为最短路径。以后每求得一条最短路径v, …, vk,就将vk加入集合S中,并将路径v, …,vk , vi与原来的假设相比较,取路径长度较小者为最短路径。重复上述过程,直到集合V中全部顶点加入到集合S中。但是迪杰斯特拉算法有一定的缺陷,它不能处理负权回路,所有才有了新的算法Bellman-Ford,它的算法思想是构造一个最短路径长度数组序列dist[],然后采用递推公式求得顶点到源点的最短路径。对于Bellman-Ford算法的改进就是用邻接表存储图,这样可以使算法复杂度降低。还有一个算法SPFA算法,其实它是Bellman-Ford算法的一种队列实现,减少了冗余。它的具体算法是:1.队列Q={s};2.取出队头u,枚举所有的u的临边。若d(v)>d(u)+w(u,v)则改进,pre(v)=u,由于d(v)减少了,v可能在以后改进其他的点,所以若v不在Q中,则将v入队;3.一直迭代步骤2,直到队列Q为空(正常结束),或有的点的入队次数>=n。所以在做题的时候要衡量一下选哪一个算法比较容易或者比较简便,不容易超时。在做最短路径问题的时候,最让我印象深刻的是求安全度的那个,它的题目是这样的:XX星球有很多城市,每个城市之间有一条或多条飞行通道,但是并不是所有的路都是很安全的,每一条路有一个安全系数s , s是在 0 和 1 间的实数(包括0,1),一条从u 到 v 的通道P 的安全度为Safe(P=s(e1)*s(e2)…*s(ek) e1,e2,ek是P 上的边,现在8600 想出去旅游,帮8600在这么多的路中选择一条最安全的路。刚开始看这道题的时候我都蒙圈了,虽然知道寻找安全的路就是让Safe最大,但是脑子里想的就是求图上的最长的路,其实仔细分析一下并不是这个样子,这道题就是一个变形的最短路径,因为s是0到1之间的一个数,如果使安全系数最大则相乘的数越少越好,如果多乘以一个数的话就要将小数点往左移一位,数字变小,所以是变相求最短路径。感觉这些题出得真是挺有意思的。

   在这门课程实验过程中,我总结了一下我在写程序时犯的一些错误或者是编程遇到的问题。首先因为没有学过C语言,所以在输入输出的时候总是用cout和cin,虽然c++的输入输出流用起来很方便,但是速度比C语言的要慢的多,在输入输出量大的时候总是会超时,有的时候也会考虑用到scanf,但是用的时候也会出错,比如那次写While(scanf(…)!=0)的时候程序出现死循环,原来是因为程序结束符EOF的值是-1而不是0,(从这个方面就可以看出自己学到的知识还是太少,在自己完全不了解一门知识的时候应该找时间去学习它),自己掌握的知识比较少不可怕,因为有机会可以补回来,但是自己比较心急,在还没有读完题目的时候就开始去做,以致于有的时候搞不清楚程序是要求处理多组数据还是一组数据,或者是自己在用到哪些函数的时候忘记写它的头文件,比如sort函数需要#include<algorithm>头文件、memset需要#include<string>头文件等等;还有一个问题就是数组越界,在HDU上面得到的是Runtime Error的信息反馈,有时候是运算中的数据溢出收到Wrong Answer的错误提示,自己在认真检查之后也不易发现。在最后一个专题当中,我提交的程序总是会Runtime Error,原来是定义超大的数组的时候定义成了局部变量,只要定义成全局变量就没问题了。我上网查了关于这方面的信息,是因为局部变量分配在栈比较小,而全局变量分配到堆比较大。

    这次的课程让我了解并学习了贪心算法、搜索、动态规划和图论这四个专题,老师说,学习ACM需要有强烈的兴趣、睿智的思想和不懈的努力,我发现自己现在的自己是在初级阶段,好像这三点都不怎么具备,因为到了大学就失去了目标,感觉自己好像失去了方向,知不道自己的兴趣点在哪里,但是我在强迫着自己去学ACM,学着培养自己的兴趣,我选择了ACM这门课,就是希望自己能够对编程感兴趣,提高自己的解题能力,然后学到一些平时接触不到的的知识。另外,我发现学习是相通的,这个学期我们学习了数据结构,其中就讲到了图论和搜索,上学期学的离散数学也讲到了这两点知识,而且这几个专题之间也有联系,比如:求最小生成树的算法Prim算法和Kruskal算法就是贪心算法的正确范例。在学习的过程中,不仅让我对学过的知识又重新温习了一遍,还为今后的一些科目像大三要学的算法设计与分析打下基础。虽然这个过程很艰苦,当别人的选修课只需要读读背背的时候,我们要敲代码到深夜,而且在敲代码的过程中,我还遇到了许多Bug和知识上的缺陷,尤其是大一时候c++课上遗漏的知识点,但是有失必有得,这些Bug和知识都能够通过网络解决同时还丰富了自己的知识量,我感觉挺开心的。尤其是当你自己写的程序AC过去,那种喜悦是别人所不能够体会的。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值