ACM课程学习总结报告
通过一个学期的ACM课程的学习,我学习了到了许多算法方面的知识,感受到了算法知识的精彩与博大,以及算法在解决问题时的巨大作用。此篇ACM课程学习总结报告将从以下方面展开:
学习ACM算法知识之前的具备的知识基础
学习过程及知识梳理
心得体会及收获
一,学习ACM算法知识之前具备的知识基础
在开始这一学期的课程之前,大一上学期及寒假期间我学习了C++标准库中的STL,了解了一些通用操作,各种类型的容器的特性,以及一些算法。关于算法,只学习了一些简单的遍历,递归。并未深入学习。
二,学习过程及知识梳理
1. 递归
递归并不是在ACM课上学习的,是在C++的课上得到了讲解。写在这里是因为我觉的递归是很重要的一种方法或者说思想。在上学期我自己了解了一下递归,自以为已经掌握了递归。在这一学期开始深入了解之后发现有一些有趣的地方。
首先,递归的具体含义是:递归_计算机科学(来自维基百科)。
这篇文章说的很清楚:
递归是通过重复将问题分解为同类的子问题而解决问题的方法
具体在C/C++中,是指在函数中调用函数本身。
在我看来,递归与循环有相似之处,都是下一轮与上一轮进行同样的操作,所以有时候递归可以与循环相互转化。但循环与递归也有不同之处,循环是单向的,递归有一个返回过程,而且,递归是从结果出发的,方向不同;另外,递归可以用于在计算开始不确定层数的情况,而循环则不可以;
我觉得这句话说的很好:
递归的强大之处在于它允许用户用有限的语句描述无限的对象。因此,在计算机科学中,递归可以被用来描述无限步的运算,尽管描述运算的程序是有限的。
——尼克劳斯·维尔特
但我认为在C/C++中可以用循环(递推)的地方应该避免用递归,因为C/C++中的递归每产生新的一层运算就要产生新的堆栈,这无疑降低了效率。
这也是我在刚开始学习的时候觉得递归无用的原因,但在后面的学习中,我发现有很多算法用递归来理解更加容易方便,更接近数学。比如在搜索的相关算法的学习中,递归实现就重要。
2.贪心算法与动态规划
贪心与动态规划有许多相似之处
费老师在一开始讲贪心时就说,贪心是最容易的算法了,贪心的题目也是可遇不可求的。。。。贪心理解是一种在每一步选择中都采取在当前状态下最好或最优(即最有利)的选择,从而希望导致结果是最好或最优的算法。适用于子问题的最优解组成了最终问题的最优解的情况,也就是说具有最优子结构的问题。
贪心算法的经典模型:
(1)背包问题(最优装载):
给定n个物品和一个容量为C的背包,物品i的重量是Wi,其价值为Vi,背包问题是如何选择入背包的物品,使得装入背包的物品的总价值最大。
在背包问题中可以将物品的一部分装入背包,但不能重复装入。
背包问题的贪心原则就是尽可能选择单位重量价值最大的物品。
解决背包问题时首先按照物品的”性价比“(Vi/Wi)由大到小排序,然后顺序选择物品直至背包装满。
(2)区间相关问题
区间问题是可抽象为多个线段之间的关系的一类问题,不同的类型的区间相关问题有着不同的贪心原则,具体在程序上就表现在线段区间的不同排序方式以及可行区间的筛选方式。
就我刷过的题目来看,区间相关问题模板化很强,基本只要将问题抽象成区间(线段)之间
的关系,运用不同的排序原则及不同的贪心原则选择可行区间即可。
3.搜索
搜索是很重要的一种算法,应用范围很广,而且在之后图论的学习中是很关键的知识点,在刘汝佳的紫书中是将搜索放在了图论专题之下,很多资料也是将搜索作为图论的一个子专题。
老实说,我的搜索掌握的不好。所以正好趁写这个报告的机会,将搜索专题总结复习一下。
搜索分为DFS(深度优先搜索),BFS(广度优先搜索)(BFS与DFS对图进行搜索),此学期ACM课程还介绍了两种方法二分法与三分法;
(1)BFS
搜索完一层之后才进行下一层的搜索工作,队列的先进先出性质可很好的实现BFS。
首先将队首元素(未到达节点)取出;拓展此节点并经拓展后产生的新节点放入队列(无法产生新节点则直接删除队首元素);删除队首元素;循环进行以上步骤直至队列为空
BFS找到的问题的解一定是最优解。
下面是广搜的伪代码,BFS是一种层次遍历,无需用到递归即可方便的实现:
While 队列不为空 do
Tmp <- 队首元素
从Tmp循环拓展下一个状态Next
If 状态Next合法 then
生成新状态Next
Next.Step = Tmp.Step + 1
将Next放入队列
End
删除队首元素
End
广度优先遍历演示地址:
http://sjjg.js.zwu.edu.cn/SFXX/sf1/gdyxbl.html
(2)DFS
DFS是按照从初始状态生成下一层的任一节点,判断目标状态是否出现;若未出现,返回上一层继续上一步骤,直至不能产生新的状态的节点;若仍不能找到目标状态,则返回上一层,至上一层的下一个节点继续扩展;重复以上步骤,直至找到目标状态。
相较于BFS,DFS可以更快的找到目标状态,但有时找到的解不一定是最优解
很明显DFS运用了递归的思想,可以运用递归来实现
DFS的递归实现与非递归实现都运用了栈的先进先出的性质,类似于BFS利用队列来实现,DFS利用栈来实现的思路是:
1 每次取出栈顶元素,对其进行拓展。
2 若栈顶元素无法继续拓展,则将其从栈中弹出。继续1过程。
3 不断重复直到获得目标状态(取得可行解)或栈为空(无解)。
具体的实现的伪代码为:
非递归实现:
While 栈不为空 do
Tmp <- 栈顶元素
从Tmp拓展下一个未拓展的状态Next
If 没有未拓展状态(到达叶节点) Then
删除栈顶元素
Else If 状态Next合法 Then
将Next压入栈
End
递归实现:
Function Dfs (Int Step, 当前状态)
Begin
可加结束条件
从当前状态循环拓展下一个状态Next
If 状态Next合法 Then
Dfs (Step + 1, Next ))
End
(3)二分搜索与三分搜索
二分搜索与三分搜索的原理都很简单,即不断缩小搜索区间,直至找到解
二分搜索要求搜索对象单调且无重复;
三分搜索是在二分搜索的基础上对右区间或左区间再进行一次二分,适用于凸性函数,即是该函数必须有一个最大值(或最小值),在最大值(最小值)的左侧序列,必须满足不严格单调递增(递减),右侧序列必须满足不严格单调递减(递增)。
为了提高效率,二分搜索与三分搜索一般使循环实现,不使用递归
4.动态规划
就我而言,这学期最难的一块内容就是动态规划了,只要问题有具有最优子结构就可使用动态规划来解决。使用动态规划解决问题的过程中,最关键的无疑是找到子问题的分解方式与状态转移方程了。有的简单问题可以递推直接解决这两个问题,而有关一些复杂的问题,可以找到许多经典模型:
(1)背包类问题
背包类问题的范围很大,从最基础的01背包与完全背包,衍生出许许多多的背包类问题。《背包九讲》写的很好,给了我很大启发。具体的思路与代码实现就不在此报告里赘述了。背包问题的变化很丰富,要注意分辨清楚
( 2 ) 最大递增子序列与最长公共子序列
最大递增子序列问题的状态转移方程为:F(k) = Max { F(i); 1<i < k 且 a[i] < a[k] } + 1
最长公共子序列的递推公式为
5.图论
图论与dp是这学期最大的两个专题了。在这一专题里,首先了解了图数据结构的一些基础知识以及图的存储方式,我感到最有趣的就是邻接表的数组表示形式了,开销低且高效,感觉甚是神奇,下面贴出邻接表的数组表示形式:
struct edge
{
int x, y, nxt; typec c;
} bf[E];
void addedge(int x, int y, typec c)
{
bf[ne].x = x; bf[ne].y = y; bf[ne].c = c;
bf[ne].nxt = head[x]; head[x] = ne++;
}
基础知识学习了解之后,又学习了并查集,通过对并查集实现的循序渐进的一次次优化,愈加感觉到思考的力量,我觉得这是这学期ACM课程中最有趣的一部分了。
学习完了并查集,又学习了最小生成树的几种经典算法:prim算法,kruskal算法
及解决最短路问题的三种算法:Dijkstra算法,Bellman-Ford算法,spfa算法(Bellman-Ford算法的队列实现)
至此,这学期的ACM课程就结束了。
三,心得体会及收获
收获当然是以上提到的算法知识了,体会到了算法是很有趣的,更重要的是初步学习到了如何解决问题(不敢说完全学习到了。。。)。这东西比较虚,但在做应用软件时,明显体会到了学过ACM或者说算法的优势:不仅仅给我增加了自信,思考问题也更加深入,同时代码实现的能力也好一些。
除了以上这些,通过ACM课程我还认识了许多比自己强,比自己努力的人;也通过各种渠道了解到了山农之外的牛人是多么牛。我意识到自己之前看到的那片天空是多么渺小,为那些不能称之为成绩的“成绩”而沾沾自喜是多么幼稚;这给了我努力的动力与静下心来的理由。这或许是对我改变最大的一点吧。
2016年6月30日
刘占山