Acm课程总结
Acm一开始了解这个是大一的时侯听说有一门特殊的选修课可以在大一选,同时一些学长学姐也说这门课挺难的也很有用,然后我就抱着试试的心态大一去上了前几节,说真的因为是学期末开的课比较突然前几节课其实具体实现并没有很懂只是听懂了一些算法的思想,后来由于我们测试的线性代数与课程冲突,翘了几节线代后线代老师允许了,同时发现线代本身需要学习,于是就放弃了。大二学java的时候想写点小程序练练手,当时觉得还是写游戏比较好然后就尝试着写扫雷,画完界面以后逻辑部分一直没法实现,后来偶然想起搜索来写了写试了试结果成功了,一开始觉得这门课挺没用的现在真正用上以后发现这门课还是很有用的。
大二这学期这门课被当成预置选修课,然后听到各种传言什么老师只是选拔队员一般人不要,尽早退,给挂科之类的,但是想到上学期真正用上了一些算法,所以尽管很多人都退了我还是没退这门课。
学完这门课,总结一下学到的东西:
第一首先是对stl有了更深的理解,因为暑假期间刷了一些题大多都是用的stl,大一时候老师为了提高我们的代码能力stl不让用,所有的东西都是自己想用基本语句实现,所以虽然stl看过一部分加上将近一年没用,实际忘了不少,刷第0专题的题的时候stl用了不少
对stl中的各种容器也有了一定的了解,多亏学的时候多查了些东西,这个也是对本学期数据结构打了个基础。
Stl:
1.栈 (Stack),stack是一种先进后出(First In Last Out, FILO)的数据结构,它只有一个出口,只能操作最顶端元素。
c++栈的头文件: #include
它的定义方法:stack
可以对其的操作有empty() 返回bool型,表示栈内是否为空;
size()返回栈内元素个数;top()返回栈顶元素;
pop()移除栈顶元素push(data_type a) 向栈压入一个元素。
2.队列(queue),queue是一种先进先出(First In First Out, FIFO)的数据结构,从底端加入元素,从顶端取出元素。
它的头文件: #include
它的定义方法:queue
他的操作empty() 、size() 和栈一样;
front() 返回queue内的下一个元素;
back() 返回queue内的最后一个元素;
pop() 移除queue中的一个元素;
push(data_type a) -- 将一个元素a置入queue中。
3.Vector-动态数组头文件: #include
它的定义方法:vector
具体操作:empty() 回bool型,表示vector是否为空 ;
size() -- 返回vector内元素个数;push_back(data_type a) 将元素a插入最尾端;
pop_back() 将最尾端元素删除;
v[i] 类似数组取第i个位置的元素;
4.Vector-动态数组。
头文件: #include
定义:queue
如:queue
操作:
empty() -- 返回bool型,表示queue是否为空 (q.empty() )
size() -- 返回queue内元素个数 (q.size() )
front() -- 返回queue内的下一个元素 (q.front() )
back() -- 返回queue内的最后一个元素(q.back() )
pop() -- 移除queue中的一个元素(q.pop(); )
push(data_type a) -- 将一个元素a置入queue中(q.push(a); )
5.Sort
头文件: #include
sort(begin, end);
sort(begin, end, cmp);
默认参数升序排列;
Cmp函数:
Bool cmp(node a,node b)
{ Return a>b;}
6.生成排列
头文件: #include
bool next_permutation(begin, end);
改变区间内元素的顺序,产生下一个排列。
bool prev_permutation(begin, end);
产生前一个排列。
end为最后一个元素的下一个位置。
7.upper_bound 和 lower_bound
upper_bound(begin, end, value);
返回可插入值为value的元素的第一个位置。
lower_bound(begin, end, value);
返回可插入值为value的元素的最后一个位置。
8.set 和 multiset
set 和 multiset会根据特定的排序准则,自动将元素排序,两者的不同之处在于multiset可以允许元素重复而set不允许元素重复。
头文件: #include
定义:set
如:set
如果想按照自己的方式排序,可以重载小于号。
struct new_type{
int x, y;
bool operator < (const new_type &a)const{
if(x != a.x) return x < a.x;
return y < a.y;
}
}
set
操作:
s.insert(elem) -- 安插一个elem副本,返回新元素位置。
s.erase(elem) -- 移除与elem元素相等的所有元素,返回被移除 的元素个数。
s.erase(pos) -- 移除迭代器pos所指位置上的元素,无返回值。
s.clear() -- 移除全部元素,将整个容器清空。
迭代器举例:
multiset
for(pos = s.begin(); pos != s.end(); pos++)
9.map和multimap
所有元素都会根据元素的键值自动排序,map的所有元素都是pair,pair的第一个元素被视为键值,第二个元素为实值。map不允许两个元素有相同的键值,但multimap可以。
头文件: #include
定义:map
如:map
操作:
m.size() 返回容器大小
m.empty() 返回容器是否为空
m.count(key) 返回键值等于key的元素的个数
m.lower_bound(key) 返回键值等于key的元素的第一个可安插的位置
m.upper_bound(key) 返回键值等于key的元素的最后一个可安插的位置
m.begin() 返回一个双向迭代器,指向第一个元素。
m.end() 返回一个双向迭代器,指向最后一个元素的下一个 位置。
m.clear() 讲整个容器清空。
m.erase(elem) 移除键值为elem的所有元素,返回个数,对 于map来说非0即1。
m.erase(pos) 移除迭代器pos所指位置上的元素。
直接元素存取:
m[key] = value;
查找的时候如果没有键值为key的元素,则安插一个键值为key的新元素,实值为默认(一般0)。
m.insert(elem) 插入一个元素elem
a)运用value_type插入
map
m.insert(map
b) 运用pair<>
m.insert(pair
c) 运用make_pair()
m.insert(make_pair("Robin", 22.3));
10.优先队列(priority_queue)
一个拥有权值观念的queue,自动依照元素的权值排列,权值最高排在前面。缺省情况下,priority_queue是利用一个max_heap完成的
头文件: #include
定义:priority_queue
如:priority_queue
操作:
q.push(elem) 将元素elem置入优先队列
q.top() 返回优先队列的下一个元素
q.pop() 移除一个元素
q.size() 返回队列中元素的个数
q.empty() 返回优先队列是否为空
第一个专题贪心专题,贪心算法个人的理解是本算法总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的仅是在某种意义上的局部最优解。贪心算法不是对所有问题都能得到整体最优解,但对范围相当广泛的许多问题他能产生整体最优解或者是整体最优解的近似解。我认为他是循环结构的延伸,所谓的for+if的形式通过某种度量来找出当前的最优在用此度量找到所有最优解的最优。这个专题刷题中印象比较深的几个问题莫过于背包问题,活动安排问题,钓鱼问题,单纯策略的田忌赛马问题。
使用贪心算法求解问题应该考虑如下几个方面:
(1)候选集合A:为了构造问题的解决方案,有一个候选集合A作为问题的可能解,即问题的最终解均取自于候选集合A。
(2)解集合S:随着贪心选择的进行,解集合S不断扩展,直到构成满足问题的完整解。
(3)解决函数solution:检查解集合S是否构成问题的完整解。
(4)选择函数select:即贪心策略,这是贪心法的关键,它指出哪个候选对象最有希望构成问题的解,选择函数通常和目标函数有关。
(5)可行函数feasible:检查解集合中加入一个候选对象是否可行,即解集合扩展后是否满足约束条件。
背包问题相对思想比较简单,有两类背包问题(根据物品是否可以分割),如果物品不可以分割,称为0—1背包问题(动态规划);如果物品可以分割,则称为背包问题(贪心算法)。
此类问题有3种方法来选取物品:
(1)当作0—1背包问题,用动态规划算法,获得最优值220;
(2)当作0—1背包问题,用贪心算法,按性价比从高到底顺序选取物品,获得最优值160。由于物品不可分割,剩下的空间白白浪费。
(3)当作背包问题,用贪心算法,按性价比从高到底的顺序选取物品,获得最优值240。由于物品可以分割,剩下的空间装入物品3的一部分,而获得了更好的性能。
本质:按照某种条件排序,然后再根据限制条件来进行选择。
田忌赛马问题,本类问题逻辑比背包问题复杂(选取策略种类多,情况多),但是基本思想还是贪心,选取当前的最优策略。这种题目主要考审题:
(1)分析好策略种类
(2)分析好各个策略的选定条件;
(3)循环求解。
多处最优服务次序问题
对服务时间最短的顾客先服务的贪心选择策略。
首先对需要服务时间最短的顾客进行服务,即做完第一次选择后,原问题T变成了需对n—1个顾客服务的新问题T’。
新问题和原问题相同,只是问题规模由n减小为n—1。
基于此种选择策略,对新问题T’,在n—1个顾客中选择服务时间最短的先进行服务,如此进行下去,直至所有服务都完成为止。
删数问题本问题采用下降点优先的贪心策略
(1)虽然删去1位数后,把原问题T变成了n-1位中删除k-1位的问题
(2)循环继续
第二专题搜索专题本专题印象最为深刻,首先由于写过一些小程序用到了本专题的算法,另外数据结构课程刚好也有部分的内容与之相重叠。什么事搜索算法:搜索算法是利用计算机的高性能来有目的地穷举一个问题的部分或所有的可能情况,从而求出问题的解的一种方法。
相比于单纯的枚举算法有了一定的方向性和目标性。算法是在解的空间里,从一个状态转移(按照要求拓展)到其他状态,这样进行下去,将解的空间中的状态遍历,找到答案(目标的状态)。
那什么是状态?什么事状态转移?状态(state)是对问题在某一时刻进展情况的数学描述,或者是数学抽象。每一个状态都会是答案的一个“可能的”解。状态的转移就是问题从一个状态转移到另一个状态,这样就可以进行搜索的一步步延伸,最后要得到的解也是其中的一个状态。
1.广度优先搜索(BFS)
基本思想:从初始状态S 开始,利用规则,生成所有可能的状态。构成的下一层节点,检查是否出现目标状态G,若未出现,就对该层所有状态节点,分别顺序利用规则。(对于二叉树来说相当于层序遍历顺序)。
生成再下一层的所有状态节点,对这一层的所有状态节点检查是否出现G,若未出现,继续按上面思想生成再下一层的所有状态节点,这样一层一层往下展开。直到出现目标状态为止。这样利用队列先进先出(FIFO)的性质恰好可以来完成这个任务。
具体过程:
(1) 每次取出队列首元素(初始状态),进行拓展
(2) 然后把拓展所得到的可行状态都放到队列里面
(3) 将初始状态删除
(4) 一直进行以上三步直到队列为空。
广度优先搜索框架
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
2.深度优先搜索(DFS)
基本思想:从初始状态,利用规则生成搜索树下一层任一个结点,检查是否出现目标状态,若未出现,以此状态利用规则生成再下一层任一个结点,再检查,重复过程一直到叶节点(即不能再生成新状态节点),当它仍不是目标状态时,回溯到上一层结果,取另一可能扩展搜索的分支。采用相同办法一直进行下去,直到找到目标状态为止。符合栈的先进后出(FILO)的性质。
具体实现过程
1 每次取出栈顶元素,对其进行拓展。
2 若栈顶元素无法继续拓展,则将其从栈中弹出。继续1过程。
3 不断重复直到获得目标状态(取得可行解)或栈为空(无解)。
深度优先搜索框架
递归实现:
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()
Else If 状态Next合法 Then
Stack.push(Next)
End
第三个专题是动态规划对本专题的认识主要是,解题流程固定,题目难度不一,题目说水可以很水(斐波那契数)说难可以很难状态转移方程难找,这个专题考的主要是数学问题(毕竟数学很大很大的部分是动态规划),不想其他专题考的是变成思想,本专题的数学能力考的比较多。
Acm中动态规划是解决多阶段决策问题的一种方法。多阶段决策问题:如果一类问题的求解过程可以分为若干个互相联系的阶段,在每一个阶段都需作出决策,并影响到下一个阶段的决策。
多阶段决策问题,就是要在可以选择的那些策略中间,选取一个最优策略,使在预定的标准下达到最好的效果.
最优性原理
不论初始状态和第一步决策是什么,余下的决策相对于前一次决策所产生的新状态,构成一个最优决策序列。
最优决策序列的子序列,一定是局部最优决策子序列。
包含有非局部最优的决策子序列,一定不是最优决策序列。
动态规划的指导思想
在做每一步决策时,列出各种可能的局部解
依据某种判定条件,舍弃那些肯定不能得到最优解的局部解。
以每一步都是最优的来保证全局是最优的。
动态规划的基本模型
动态规划问题具有以下基本特征:
问题具有多阶段决策的特征。
每一阶段都有相应的“状态”与之对应,描述状态的量称为“状态变量”。
每一阶段都面临一个决策,选择不同的决策将会导致下一阶段不同的状态。
每一阶段的最优解问题可以递归地归结为下一阶段各个可能状态的最优解问题,各子问题与原问题具有完全相同的结构。什么是动态规划?动态规划实际上就是一种排除重复计算的算法,更具体的说,动态规划就是用空间换取时间。
动态规划的几个概念
阶段:据空间顺序或时间顺序对问题的求解划分阶段。
状态:描述事物的性质,不同事物有不同的性质,因而用不同的状态来刻画。对问题的求解状态的描述是分阶段的。
决策:根据题意要求,对每个阶段所做出的某种选择性操作。
状态转移方程:用数学公式描述与阶段相关的状态间的演变规律。
动态规划问题的一般解题步骤
1、判断问题是否具有最优子结构性质,若不具备则不能用动态规划。
2、把问题分成若干个子问题(分阶段)。
3、建立状态转移方程(递推公式)。
4、找出边界条件。
5、将已知边界值带入方程。
6、递推求解。
最后一部分是图论,这部分时间较短加上临近考试周,相对去其他的专题投入的心力少,但是由于学过离散数学对图的概念已经基本了解,同时真学期本身的数据结构也与这部分有很大的重叠部分,虽然投入的精力少,但这部分学起来还是相对简单的。总体感觉这部分的题目相对固定,算法变化少。
图的基本算法:深搜,广搜。
并查集
英文:Disjoint Set,即“不相交集合”
将编号分别为1…N的N个对象划分为不相交集合,
在每个集合中,选择其中某个元素代表所在集合。
常见两种操作:
合并两个集合
查找某元素属于哪个集合
find2(x) merge2(a, b)
{ {
r = x; if (a
while (set[r] != r) set[b] = a;
r = set[r]; else
return r; set[a] = b;
} }
最短路径算法:
Prim算法:
(1) 任意选定一点s,设集合S={s}
(2) 从不在集合S的点中选出一个点j使得其与S内的某点i的距离最短,则(i,j)就是生成树上的一条边,同时将j点加入S
(3) 转到(2)继续进行,直至所有点都己加入S集合。
Kruskal算法:
将边按权值从小到大排序后逐个判断,如果当前的边加入以后不会产生环,那么就把当前边作为生成树的一条边。
最终得到的结果就是最小生成树。
总结一下感觉acm本身用处还是很大的,现阶段对于思维开阔,编写小游戏,提高变成能力很有帮助,而且acm本身能让你真正的获得一些成就感,尤其是在刷完题后,比起你简单的做个系统,做个网页,带来的成就感更大。我很庆幸选择了这一门选修课!