ACM程序设计课程总结报告

来源:互联网 发布:spss如何导入excel数据 编辑:程序博客网 时间:2024/05/16 04:08

一.前言:

   本学期选修选择了ACM程序设计这门课,当初选择这门课是因为,通过上学期C语言的学习,感觉自己对这个方向比较有兴趣,但是学的内容非常基础,自己想要真正去了解这方面的内容,跟着费老师继续深入学习,提高自己的专业能力。虽然觉得自己的水平参加比赛的希望很渺茫,还是想努力提高自己的水平和能力。通过本学期的学习,我觉得这门课程对我的编程能力很有帮助,更重要的是,在ACM这门课中学到了一些基础算法,开发了自己的思想,这些是在平时的学习中学习不到的。

   课程初始,感觉学习的内容比较基础,容易接受,但是到了后面的学习,难度开始增加,对我而言理解起来难度很大。课上跟着老师的思路还可以理解一些,到了课下自主完成题目时就感觉到力不从心,经常耗费几个小时一道题目都完成不了。

   对于这门课,我很喜欢老师的授课方式以及课下的学习方式,理论加实验上机课,课下自主学习就是刷题,自己做,做完后及时自己总结,并且写成博客发表。在ACM这门课上,学的最多的还是思想,我觉得在费老的课上学到了一种很开放的思想,而且在讲解的同时带入日常情境,加深理解,还有解题的思路,考虑事情的方法,这些东西不管是在学习上还是别的方面都很适用。

二.知识小结:

1.STL

(1)STL(StandardTemplate Library),即标准模板库,是一个高效的C++程序库。它被容纳于C++标准程序库(C++ Standard Library)中。

(2)常用容器:

:stack是一种先进后出的数据结构,它只有一个出口,只能操作最顶端元素。

头文件: #include <stack>

操作:

empty()-- 返回bool型,表示栈内是否为空 (s.empty() );

size()-- 返回栈内元素个数(s.size() )

top()-- 返回栈顶元素值(s.top() )

pop()-- 移除栈顶元素(s.pop();)

push(data_typea) -- 向栈压入一个元素a(s.push(a);)

队列:queue是一种先进先出(First In First Out, FIFO)数据结构,从底端加入元素,从顶端取出元素。

头文件: #include <queue>

操作:empty() -- 返回bool型,表示queue是否为空(q.empty() ); 

size()-- 返回queue内元素个数 (q.size());

front()-- 返回queue内的下一个元素 (q.front());

back()-- 返回queue内的最后一个元素(q.back());

pop()-- 移除queue中的一个元素(q.pop(););p

ush(data_typea) -- 将一个元素a置入queue中(q.push(a); );

动态数组:

头文件: #include <vector>

操作:empty() -- 返回bool型,表示vector是否为空 (v.empty() );

size()-- 返回vector内元素个数 (v.size() );

push_back(data_typea)将元素a插入最尾端;

pop_back()将最尾端元素删除;

set和multiset

头文件: #include <set>

操作:s.insert(elem) -- 安插一个elem副本,返回新元素位置;

s.clear()-- 移除全部元素,将整个容器清空;

s.size()-- 返回容器大小。s.empty()--返回容器是否为空;

s.begin()-- 返回一个双向迭代器,指向第一个元素;

s.end()-- 返回一个双向迭代器,指向最后一个元素的下一个位置;

map和multimap

头文件: #include <map>

操作:m.size() 返回容器大小m.empty();

返回容器是否为空m.count(key)

返回键值等于key的元素的个数;m.begin() 返回一个双向迭代器,指向第一个元素。   

m.end()返回一个双向迭代器,指向最后一个元素的下一个位置;

m.clear()讲整个容器清空。m.erase(elem)移除键值为elem的所有元素,返回个数,对于map来说非0即1;

m.erase(pos)移除迭代器pos所指位置上的元素;

优先队列:一个拥有权值观念的queue,自动依照元素的权值排列,权值最高排在前面。缺省情况下,priority_queue是利用一个max_heap完成的

头文件: #include <queue>

操作:q.push(elem) 将元素elem置入优先队列;

q.top()返回优先队列的下一个元素;

q.pop()移除一个元素q.size()返回队列中元素的个数;

q.empty()返回优先队列是否为空;

(3)常用算法:

sort函数:默认的sort函数是按升序排。对应于sort(a,a+n);两个参数分别为待排序数组的首地址和尾地址
可以自己写一个cmp函数,按特定意图进行排序。
例如:
int cmp( const int &a, const int &b )

{
if( a > b )
return 1;
else
return 0;
}
sort(a,a+n,cmp);
是对数组a降序排序。

lower_bound:  

返回一个ForwardIterator,指向在有序序列范围内的可以插入指定值而不破坏容器顺序的第一个位置。重载函数使用自定义比较操作。
upper_bound:             

返回一个ForwardIterator,指向在有序序列范围内插入value而不破坏容器顺序的最后一个位置,该位置标志一个大于value的值。

(4)STL总结:

学习时感觉内容很多,与之前学习的C也有一定的差别,但是最后发现,我们所了解的几个容器,它们的一些基本操作非常相似。但是想要熟练地使用,必须抓住它们的核心,重在理解。而且STL是后续学习的基础,只有打牢基础,以后学习才会高效。例如sort排序,简单快速,上学期的时候学习C,刷题时有很多排序题,比如冒泡原理,选择排序之类的,当时刚学习,用的是for循环,有时候循环太多自己都转晕了,后来接触了STL,用sort排序,提高了效率,节省时间,简化代码。STL是很重要的一个模块,很实用很方便。

2.递推递归算法

(1)概述:

递归是大问题转化为小问题,不断调用自身或不断间接调用的一类算法

递归算法的关键是:

1.要找出大问题和小问题的联系----递归定义。

2.递归终止条件。

递推是一种用若干步可重复运算来描述复杂问题的方法.

递推的关键是想出递推公式,一般递推公式是从后面向前推,当然也有很多是从中间或者从前向后推。

(2)总结:

  做这些题目主要是找其中规律,比较简单的思路就是列出前几项,找其中的规律,以 此推到后面,而且很多题目都需要和数学知识进行联系。但感觉比较难的题目,很多时候就像 老师说的,需要灵感。对此,也没有什么特别好的办法,主要还是通过做题积累经验吧。

3.动态规划:

(1)概述:动态规划是解决多阶段决策问题的一种方法。

(2)基本性质:

最优子结构性质:如果问题的最优解所包含的子问题的解也是最优的,我们就称该问题具有最优子结构性质(即满足最优化原理)。最优子结构性质为动态规划算法解决问题提供了重要线索。

子问题重叠性质:子问题重叠性质是指在用递归算法自顶向下对问题进行求解时,每次产生的子问题并不总是新问题,有些子问题会被重复计算多次。动态规划算法正是利用了这种子问题的重叠性质,对每一个子问题只计算一次,然后将其计算结果保存在一个表格中,当再次需要计算已经计算过的子问题时,只是在表格中简单地查看一下结果,从而获得较高的效率。

无后效性将各阶段按照一定的次序排列好之后,对于某个给定的阶段状态,它以前各阶段的状态无法直接影响它未来的决策,而只能通过当前的这个状态。换句话说,每个状态都是过去历史的一个完整总结。这就是无后向性,又称为无后效性。

(3)解题思路:

    1.判断问题是否具有最优子结构性质,若不具备则不能用动态规划。
   2.
把问题分成若干个子问题(分阶段)。
   3.建立状态转移方程(递推公式)。
   4.找出边界条件。
   5.将已知边界值带入方程。
   6.递推求解。

(4)指导思想:

  1.在做每一步决策时,列出各种可能的局部解

  2.依据某种判定条件,舍弃那些肯定不能得到最优解的局部解。

  3.以每一步都是最优的来保证全局是最优的。

(6)分类解析:

最长递增子序列.

例题:

题意:给出序列a [1], a [2], a [3]...... a [n],计算子序列的最大总和。

思路:最大子序列是要找出由数组成的一维数组中和最大的连续子序列。方法是:只要前i项和还没有小于0子序列就一直往后扩展,否则丢弃之前的子序列开始新的子序列,同时记录各个子序列的和,最后取他们中的最大值。

背包问题.

<1>01背包.

题意:有N件物品和一个容量为V的背包。第i件物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使价值总和最大。

思路:f[i][v]表示前i件物品恰放入一个容量为v的背包可以获得的最大价值。则其状态转移方程便f[i][v]=max{f[i-1][v],f[i-1][v-c[i]]+w[i]}由这个公式做变形就可以。

<2>多重背包.

题意:有N种物品和一个容量为V的背包。第i种物品最多有n[i]件可用,每件费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

思路:这题目和完全背包问题很类似。基本的方程只需将完全背包问题的方程略微一改即可,因为对于第i种物品有n[i]+1种策略:取0件,取1件……取n[i]件。令f[i][v]表示前i种物品恰放入一个容量为v的背包的最大权值,则有状态转移方程:f[i][v]=max{f[i-1][v-k*c[i]]+k*w[i]|0<=k<=n[i]}。

<3>完全背包.

题意:有N种物品和一个容量为V的背包,每种物品都有无限件可用。第i种物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

基本思路:这个问题非常类似于01背包问题,所不同的是每种物品有无限件。也就是从每种物品的角度考虑,与它相关的策略已并非取或不取两种,而是有取0件、取1件、取2件……等很多种。如果仍然按照解01背包时的思路,令f[i][v]表示前i种物品恰放入一个容量为v的背包的最大权值。仍然可以按照每种物品不同的策略写出状态转移方程:

f[i][v]=max{f[i-1][v-k*c[i]]+k*w[i]|0<=k*c[i]<=v}.

4.二分查找:二分查找又称折半查找。在一个单调有序的集合中查找元素,每次将集合分为左右两部分,判断解在哪个部分中并调整集合上下界,重复直到找到目标元素。

主要模板是: 

mid= (high + low)/2  

if(calculate(mid)< x) low = mid;

elsehigh= mid;其判断条件是high– low > 1.0e-6

就是high与low无限接近.有时候可以用递归来做。

  二分查找原理很简单,但是边界条件容易出错,出现死循环等,要想彻底分清楚,应该要理解透彻。

5.贪心算法:

(1)概述:贪心算法是一种在每一步选择中都采取在当前状态下最好或最优的选择,希望得到结果是最好或最优的算法。

(2)求解过程:

  1.候选集合,构造集合,问题公众的所有解均来自于她。

  2.解集合S,S不断拓展,直到构成满足问题的完整解。

  3.解决函数SOLUTION检查S是否构成问题完整解。

  4.选择函数SELECT,这就是贪心策略了,这是关键。

  5.可行函数FEASIABLE检查解集合中加入一个候选对象是否可行,即扩展的约束条件。

(3)分类解析:

背包问题

贪心算法有两类背包问题(根据物品是否可以分割),如果物品不可以分割,称为0—1背包问题(动态规划);如果物品可以分割,则称为背包问题(贪心算法)。

背包问题中可以以物品数量为标准,也可以以性价比为标准,通过一次For循环即可求解。用贪心法求解背包问题的关键是如何选定贪心策略,使得按照一定的顺序选择每个物品,并尽可能的装入背包,直到背包装满。选择重量最轻的物品,这可以装入尽可能多的物品,从而增加背包的总价值。但是,虽然每一步选择使背包的容量消耗的慢了,但背包的价值却没能保证迅速的增长,从而不能保证目标函数达到最大选择价值最大的物品,可以尽可能快的增加背包的总价值,虽然每一步选择获得了背包价值的极大增长,但背包容量却可能消耗的太快,使得装入背包的物品个数减少,从而不能保证目标函数达到最大。

搬桌子问题

题意:在400个两两相对房间之间搬桌子,走廊一次只能通过一张桌子,把桌子从一个房间移到另一个房间需要10分钟。输入T表示搬桌子的组数,输入N表示每一组要搬的桌子数,接下来的N行输入桌子搬出的房间和搬入的房间。输出每一组搬桌子的最短时间。

贪心策略:因为走廊不可以同时搬运两张桌子,可以将每两个相对的门之间的走廊设为一个参数,统计每组桌子搬完后走廊的占用次数,最大占用次数乘10即为所求时间。

删数问题

题意:给定n位正整数a,去掉k位后的数字,按原来次序排列,组成一个新的整数,设计算法,求得删除数后组成新数后,最小。N位数a可以表示为x1x2x3…xn要删除k位数,使得剩下的数字组成的整数最小。

贪心策略:采用最近下降点的贪心策略,即x1<x2<x3<x4..<xi-1<xi如果xi+1<xi(下降点)则删去xi,即得到得到一个新数且这个心为n-1位中最小的N直到删除k个为止。 

(4)总结:

贪心算法不是从整体上考虑问题,它做出的选择只是某种意义上的局部最优,以局部最优得到整体最优,这就是贪心算法的大思想。贪心往往要对问题进行一定的预处理,常见的是排序。然后进入到整个问题的核心部分贪心策略,贪心策略有的时候是实现问题的转化,或者反复遍历。贪心算法往往需要STL来实现,常用有:vector,sort排序函数等。

6.搜索:

(1)常用方法:

  二分查找,三分搜索,广度优先搜索(BFS)与深度优先搜索(DFS)

(2)BFS和DFS

广度优先搜索(bfs

从初始状态S 开始,利用规则,生成所有可能的状态。构成的下一层节点,检查是否出现目标状态G,若未出现,就对该层所有状态节点,分别顺序利用规则。生成再下一层的所有状态节点,对这一层的所有状态节点检查是否出现G,若未出现,继续按上面思想生成再下一层的所有状态节点,这样一层一层往下展开。直到出现目标状态为止。

深度优先搜索(dfs

从初始状态,利用规则生成搜索树下一层任一个结点,检查是否出现目标状态,若未出现,以此状态利用规则生成再下一层任一个结点,再检查,重复过程一直到叶节点(即不能再生成新状态节点),当它仍不是目标状态时,回溯到上一层结果,取另一可能扩展搜索的分支。采用相同办法一直进行下去,直到找到目标状态为止。

(3)常见题型:

 1.排列

 2.分解(一个数有几种分解方法);对应的有合成(几个数可以组成这个数),如一个简单的背包问题:一个包可以放的重量是多少,每件物品重量是多少?、

 3.遍历。怎么走?怎么跳?有几种走法?(有障碍就用bool 型标记一下)

(4)总结:

  这类题基本两种情况,一是广搜,一个是深搜,广搜所有情况都要来一遍,确实好用。深搜取一部分,遍历了所有的,而且与动态规划很相似。相对来说,广搜用的更多,例如地图类的问题,给定一个地图,上下左右各个方向进行搜索。总的来说,只有少数题感觉思路比较好想,大部分题目都很难直接下手,要思考很久。就是看了题有些想法,但是很难下手。还有要多注意细节的处理,很多时候都是因为细节错误导致无法AC

7.图论算法:

(1)概述:

  树 (Tree)是n(n≥0) 个结点的有限集。若 n = 0,称为空树;若 n> 0,则它满足如下两个条件:有且仅有一个特定的称为根 (Root) 的结点;其余结点可分为m (m≥0) 个互不相交的有限集T1, T2,T3, …, Tm,其中每一个集合本身又是一棵树,并称为根的子树 (SubTree)。

  图(Graph)是一种复杂的非线性数据结构,由顶点集合及顶点间的关系(也称弧或边)集合组成。可以表示为:G=(V, {VR})其中 V 是顶点的有穷非空集合;VR是顶点之间关系的有穷集合,也叫做弧或边集合。弧是顶点的有序对,边是顶点的无序对。

(2)分类解析:

并查集。即“不相交集合”。将编号分别为1…N的N个对象划分为不相交集合,在每个集合中,选择其中某个元素代表所在集合。常见两种操作:合并两个集合查找某元素属于哪个集合。 

模板算法:void made(int n) //并查集的初始化

{ inti; for(i=1;i<=n;i++)  father[i] = I;}

intfind(int x)//查找父结点找到集合中的代表元素

{if(x != father[x])  

{father[x] = find(father[x]);} 

returnfather[x];}void unions(int x,int y) //两个元素合并成一个集合其实是组合

{ x= y; y = find(y);

if(x!= y){father[x] = y;}}

求最小生成树的prim算法:

Prim算法:设G=(V,E)是连通带权图,V={1,2,…,n}构造G的最小生成树的Prim算法的基本思想是:首先置S={1},然后,只要S是V的真子集,就作如下的贪心选择:选取满足条件iÎS,jÎV-S,且c[i][j]最小的边,将顶点j添加到S中。这个过程一直进行到S=V时为止。在这个过程中选取到的所有边恰好构成G的一棵最小生树。

求最小生成树的Kruscal算法 

对所有边从小到大排序;依次试探将边和它的端点加入生成树,如果加入此边后不产生圈,则将边和它的端点加入生成树;否则,将它删去;直到生成树中有了n-1条边,即告终止。将边按权值从小到大排序后逐个判断,如果当前的边加入以后不会产生环,那么就把当前边作为生成树的一条边。最终得到的结果就是最小生成树。

三.认识与感悟

一学期的ACM课程就要结束,自己也有很多的感悟。ACM全称国际大学生算法设计大赛,是一个高挑战性与高含金量同在的竞赛。通过这一学期的学习,我对这个比赛有了初步的认识,也终于可以理解它为何有如此高的参考价值。首先根据老师的讲解解出题目,然后再根据自己的理解解决其他的问题,感觉很有成就感。最重要的一点是,确实能学到很多东西,同时提高了逻辑思维能力。一学期的学习收获颇多,也很感谢费老师的耐心讲解,希望自己在后续学习中更加主动。         

 

 

 

原创粉丝点击