说起来很惭愧,对于这两个比较简单的图算法,花了我一周的时间才将将弄明白。当然,因为白天要实习的缘故,只能用每天晚上比较零散的时间来看资料。后来几天,对着别人的代码做了几道POJ上的题目,现在也只能说对它们有了一个基础的认识。
两个算法都是跟求图的有源最短路径有关。Dijkstra主要针对的是无负权值节点的图,而Bellman-Ford算法则是可以处理有负权值的有向图的最短路径问题。两者都用到了一个“松弛计算”的方法,也就是在遍历图的顶点和边的过程中修改距离数组的值,从而来找出最短路径。
Dijkstra算法针对无负权值的图,求源点到某特定点的最短距离。大概的思路是:
将图的顶点分成两个集合S,V。S中开始时只有源点,而V中是剩下的点。有一个dis[n](n为图的节点数)的数组来记录每一个点到源点的特殊距离,这个距离都是从源点只经过S中的点而到达所求点的距离。每次的操作需要用“松弛计算”更新dis,遍历完成即可得到最短距离。
而Bellman-Ford算法主要是针对有负权值的图。来判断该图中是否有负回路,或者存在最短路径的点。判断的思路,从源点出发,进行n
- 1(n为顶点数)遍历,在每次的遍历过程中,对所有的边进行遍历判断,同样是利用松弛计算的思想,dis[v]
> dis[u] + w(u,
v)不断更新dis数组的值,直到循环结束。然后就是这个算法最精彩的地方了,再对所有的边进行一次遍历,看是否存在dis[v]
> dis[u] + w(u,
v)的边,若存在,则返回FALSE,说明图中存在负回路;若不存在,则返回TRUE,dis数组记录的就是每一个点到源点的最小值。
总的来说,这两个算法还是比较容易的,但实际的应用中,个人感觉更难的是如何将现实的问题建模成这样的图,比如poj上有货币兑换的问题(POJ
1860),交换礼物的问题(POJ 1062),还有能进行时光逆转的虫洞问题(POJ
3259),这些题目开始困扰我的地方在于我不知道如何将他们转换成用这两个算法的基本模型,不知道如何读取数据来建立所需要的图。后来看了别人的分析,以及代码,才将这几道题完全搞明白。正是这样结合实际的题目,让我体会到算法的用途以及学习它的乐趣。
当然,让我纠结于这些题目的原因在于:①一开始对算法的理解并不深入,导致后来看题目,看别人的代码始终不明白;②对图的一些存储表示方式不太熟,导致自己看代码的过程中遇到不少阻碍;③还是有些过分纠缠于细节,纠结于某一两句的代码或分析,而忽视了对整个算法的理解。
这也让我一直反思这半年一个人学习ACM算法遇到的一些困扰。总感觉自己对算法的理解不好,或者就是对POJ上的一些简单题还是不会处理,或者就是纠缠于过于细小的东西而导致花费很长时间才去理解了一个不是很复杂的算法。
我想,还是自己的学习方法有些问题。比如,在学习的过程中,总是过于急功近利,巴不得一下子弄懂好多算法,做会很多题。这样,导致自己在看算法介绍的时候,会因贪多求快而看的不够,也影响自己对算法的理解,从而在后续的做题过程中要不断地反复回来看算法介绍,这样不仅浪费了时间,还使自己变得很沮丧。还有一点,就是在学习的过程中,有时候过于纠缠于某一句讲解或者代码的理解,然后在那儿死死地想,其实反而不如先将这样的小地方放过,
我想在学习算法的过程中,我更应该注重对算法的理解,对思维的锻炼,培养自己的逻辑思考能力,而不能拘泥于具体的写代码过程。当然,写代码是加深对算法理解运用的最重要路径,否则,光学不练,永远只是纸上谈兵。
所以,以后的算法学习不能贪多求快,更重要的是一定要首先将算法看懂,这个看懂的标准至少要包括:基本上理解了整个的数学原理,熟悉它的算法过程,了解它的基本复杂度,然后就是它用到的数据结构,如果自己不熟,就有必要先去了解这种数据结构。在做好这些工作,然后才是具体的实践过程,去看相应的代码模板,去做poj上的基本题目,一定要从思维上线解决这个问题,而具体的代码实现就可以先试着套模板,然后再多看看别人的代码,一定要善于学习借鉴他人的代码,任何一门新技能的学习初期都是从模仿优秀的实例开始的,不要纠缠于这一点。然后,最好是不要拘泥于某一道题,一道题,1个小时都没有思路的时候,要么先放放,要么不妨看看他人的答案,记住你的最终目标是学会一种算法,而不是去为了向谁证明自己的思维能力就一定要自己做出来。
当然,最重要的一点,一定不要急功近利,不要总想着去跟人证明自己的算法学的多好。按照自己的规划,踏踏实实地,一个算法一个算法地学下来就好。