ACM学习报告

来源:互联网 发布:电影采集源码 编辑:程序博客网 时间:2024/04/28 20:13

ACM学习报告

已经是期末了,从寒假开始接触acm到现在的图论,已经知道了许多的算法,例如:贪心、动态规划、搜索、以及图论,现在对acm程序设计这门课进行一下总结。

寒假学习了stl的相关内容,即不同容器的利用。容器为容纳数据的模板,距离类型为queue,vector,栈,set,multisets, map等容器。其中应用最多的是vector(向量),vector是最基本的数组的类模型,vector的构造函数有4种

1. 默认构造函数,构造一个初始长度为0的空向量,如 vector<int> v1 最常应用

2. 带有单个整数参数的构造函数,此参数描述了向量的初始大小。

3. 复制构造函数,构造一个新的向量,作为已存在向量的副本。

4. 打两个常数参数的构造函数,产生一个区间的向量。

Map and multimap

Map是映照容器,由一个键值和一个映照数据组成。Map中的数据结构由按红黑树的方式实现,插入元素的键值不允许重复,元素的各项数据可通过键值检索出来。如果需要用到一对来存储数据,那map是个很好的选择。集合在插入和删除操作上比vector快,就是查找或添加末尾的元素会比较慢。

Multimap与map基本相同,唯独不同的是,multimap允许插入重复的键值元素。由于允许重复键值的存在,所以,multimap的元素插入、删除与查找都与map不同。Map and multimap在处理数据搜索相比遍历短了搜索时间。

Set和multiset的模版参数:

template<class key, class compare ,  class Allocator=allocator>

第一个参数key是所存数的键的类型,第二个参数是为排序值而定义的比较函数的类型, 第三个参数是被实现的存储分配符的类型。

映射和多重映射:

映射和多重映射是基于key的存在, 提供对T类型的数据进行快速 高速的检索。

本学期学习的第一个算法是贪心算法。贪心算法是指求出当前状态的最优解,我个人的理解是将一个大的问题分开来求,将一个大问题划分为小问题,然后再求出每个小问题的最优解,然后小问题最优解合起来就是大问题的最优解。整个贪心算法没有固定的思路,只是在每道题中选取一个合适的贪心策略,利用贪心策略去求解一个题。

还记得第一个推桌子的题,想了半天还是用模拟的方法解出了问题,怎么想也想不出怎么个贪心法,当看到40多行的贪心算法解时,感到非常的震撼。一开始因为上学期选了4门课= =,这学期必修课又多,前几周只有一个下午是空的,感觉没有充足的时间去研究算法,前七周过的简直生不如死…

贪心算法牵扯到的题型有三种:背包问题、区间问题以及哈夫曼树问题。

其中的背包问题是老师讲的最多的,背包问题很经典的一个就是往背包里放置东西,只需要将符合当前最大利益的选取出来就好。感觉有些背包问题还是挺简单的,难得也不会啊= =.

贪心算法之后的第二个章节是深度搜索和广度搜索。

深度优先搜索:深度有限搜索就是在搜索树的每一层始终先只扩展一个子节点,不断的网深的地方前进知道不能继续位置,若该节点不行 ,才从当前节点返回上一级节点,沿另一点开始往下搜,以此寻找结果。简单理解就是从出事点出发,不断往深处走,如果不能继续前进就往回走一步,尝试另一条路,直到发现目标位置。这种方法即使成功也不一定找到最优的路,但好处就是记住的位置比较少。

广度优先搜索:

在深度优先搜索中,深度越大的节点越先进行搜索。如果在搜索中把算法改为按节点的层次进行搜索,本层的节点没有搜索完成时,不能对下层节点进行处理,就是深度越小的节点越先得到扩展,也就是说先搜索的节点可以先进行搜索,这种搜索方法为广度优先搜索。广度优先就是从初始点出发,把所有的可能路径都走一遍,如果里面没有目标的位置,则把所有能够到的位置都走一遍,看看有没有目标位置,如果还不行,则尝试所有3步可以到达 的位置。用这种方法就可以找到一条最短的路径

在此期间我们在用二分法查找适用于单调函数中逼近求解某点的值。如果遇到凹或凸型函数,二分法就不是很实用,所以就要用到3分法求凹点或凸点以达到求极值的目的。做法:设范围为[l,r],先取 [L,R] 的中点 mid,再取 [mid,R] 的中点 mmid,通过比较 f(mid) 与 f(mmid) 的大小来缩小范围。当最后 L=R-1 时,再比较下这两个点的值,我们就找到了答案。

1、当 f(mid) > f(mmid) 的时候,我们可以断定 mmid 一定在白点的右边。

反证法:假设 mmid 在白点的左边,则 mid 也一定在白点的左边,又由 f(mid) > f(mmid) 可推出 mmid < mid,与已知矛盾,故假设不成立。

所以,此时可以将 R = mmid 来缩小范围。

2、当 f(mid) < f(mmid) 的时候,我们可以断定 mid 一定在白点的左边。

反证法:假设 mid 在白点的右边,则 mmid 也一定在白点的右边,又由 f(mid) < f(mmid) 可推出 mid > mmid,与已知矛盾,故假设不成立。同理,此时可以将 L = mid 来缩小范围。

 

图的基本概念:

图:二元组<V,E> 称为图(graph)。 V为结点(node)点(vertex)集。 E为图中结点之间的边的集合。

简单图

环:端点重合为一点的边。

重边:两条边连接同一对顶点。

简单图:没有环和重边的图。

无向图和有向图

如果边都是双向的,则这个图叫做无向图。

如果边都是单向的,则这个图叫做有向图。

 

连通性

无向图中,如果两个顶点之间存在一条路经,就称这两个顶点是连通的。

有向图中,如果两个顶点之间相互都存在一条路,则称它们是强连通的。

如果一个图的任意两个顶点都是连通的,就称这个图是连通的。

顶点的度

无向图中,一个顶点相连的边数称为该顶点的度。

有向图中,从一个顶点出发的边数称为该顶点得出度;到达该顶点的边数称为它的入度。

顶点的最大度数称为图的度数。

路和回路

一个连接两个顶点的,顶点与边交替的序列称为路。除了起始与终止顶点,其他顶点都不相同,这样的路称为简单路径。起始与终止顶点相同的简单路径称为圈。

Prim算法的基本思想:

任取一个定点加入生成树。在那些一个断电在生成树里,另一端点不再生成树里,另一端点不再生成树里的边中,去劝最小的边,将他和另一个端点加进生成树。

重复上一步骤,直到所有的顶点都进入生成树为止。

 

Kruskal算法的基本思想:

对所有边从小到大顺序

一次试探将边和他的端点加入生成树,如果加入此边后不产生圈,则将边换个他的端点加入生成树;否则,将他删去。知道生成树中有了n-1个边,即告终止。需要考虑算法的时间复杂度。

 

Dijkstra算法

基本思想:设置一个集合S存放已经找到最短路径的顶点,S的初始状态只包含源点v,对vi∈V-S,假设从源点v到vi的有向边为最短路径。以后每求得一条最短路径v, …, vk,就将vk加入集合S中,并将路径v, …, vk , vi与原来的假设相比较,取路径长度较小者为最短路径。重复上述过程,直到集合V中全部顶点加入到集合S中。

设计数据结构 :

图的存储结构:带权的邻接矩阵存储结构  

数组dist[n]:每个分量dist[i]表示当前所找到的从始点v到终点vi的最短路径的长度。初态为:若从v到vi有弧,则dist[i]为弧上权值;否则置dist[i]为∞。

数组path[n]:path[i]是一个字符串,表示当前所找到的从始点v到终点vi的最短路径。初态为:若从v到vi有弧,则path[i]为vvi;否则置path[i]空串。

数组s[n]:存放源点和已经生成的终点,其初态为只有一个源点v。

伪代码 

1. 初始化数组dist、path和s;

2. while (s中的元素个数<n)

     2.1 在dist[n]中求最小值,其下标为k;

     2.2 输出dist[j]和path[j];

     2.3 修改数组dist和path;

     2.4 将顶点vk添加到数组s中;

局限性

Dijkstra的缺陷就在于它不能处理负权回路:Dijkstra对于标记过的点就不再进行更新了,所以即使有负权导致最短距离的改变也不会重新计算已经计算过的结果

 

图算法

树的定义和基本术语:树 (Tree) 是 n (n≥0) 个结点的有限集。若 n = 0,称

为空树;若 n > 0,则它满足如下两个条件:

  (1)  有且仅有一个特定的称为根 (Root) 的结点;

  (2)  其余结点可分为 m (m≥0) 个互不相交的有限集

T1, T2,T3, …, Tm,其中每一个集合本身又是一棵树,并称为 根的子树 (SubTree)。

图的定义和基本术语:图 (Graph) 是一种复杂的非线性数据结构,由顶 点集合及顶点间的关系(也称弧或边)集合组成。可以表示为:      

G=(V, {VR})
   其中 V 是顶点的有穷非空集合; VR 是顶点之间关系的有穷集合,也叫做弧或边集合。弧是顶点的有序对, 边是顶点的无序对。

图的储存方式——邻接矩阵:邻接矩阵使用场合

1.数据规模不大n <= 1000,m越大越好

2.稠密图最好用邻接矩阵

3.图中不能有多重边出现

图的储存方式——邻接表:

邻接表使用场合

1.顶点数很多n>1000,边数却不多。

2.采用邻接表存储后,很多算法的复杂度也都是跟边数有关。

3.连通性的问题很多情况边数不多,多采用邻接表存储方式

并查集:1. 用编号最小的元素标记所在集合;定义一个数组 n] ,其中set[i] 表示元素i 所在的集合;

2. 每个集合用一棵“有根树”表示

定义数组 set[1..n]

set[i] = i , 则i表示本集合,并是集合对应树的根

set[i] = j, j<>i, 则 j 是 i 的父节点.

避免最坏情况

1.方法:将深度小的树合并到深度大的树

2.实现:假设两棵树的深度分别为h1和h2, 则合并后的树的高度h是:

max(h1,h2), if h1<>h2.

h1+1, if h1=h2.

效果:任意顺序的合并操作以后,包含k个节点的树的最大高度不超过

路径压缩:

1.思想:每次查找的时候,如果路径较长,则修改信息,以便下次查找的时候速度更快

2.步骤:

第一步,找到根结点

第二步,修改查找路径上的所有节点,将它们都指向根结点。

 

动态规划

在多阶段决策过程,指这样一类的活动过程,问题可以按照时间顺序分解成若干相互联系的阶段,在每个阶段做出决策,全部的决策是一个总的决策序列。在每一阶段都需要做出决策,从而使整个过程达到最好的效果。

动态规划最优组合问题有如下特征:

最优子结构:问题的解的最优解包含的子问题的解相对子问题而言也是最优的

子问题的重叠:问题的一个递归算法在每个递归步骤产生分支子问题时并不是新的,而是相对部分问题解了又解。当一个递归算法一次又一次地访问同一个子问题时,我们说该问题具有重叠子问题的特征。

动态规划算法通常做以下3个步骤:

利用最优子结构定义一个关于节的目标值的递归方程。动态规划以自底向上地对每个新产生的子问题仅解以此且将其保存在一个表格中,需要时在表格中查找,且可以在常熟时间内完成查找。根据计算出的最优解的值构造对应 的最优解.

背包问题动态规划解,最基本的背包问题,特点是:每种物品只有一件,可以选择放或不放。

基本思路

用子问题定义状态:即f[i][v]表示前i件物品放入一个容器为v的背包可以获得的最大价值。则其状态转移方程便是:

F[i][v]=max{f[i-1][v],f[i-1][v-c[i]]+w[i]}

“将前i件物品放入容量为v的背包中”这个子问题,若只考虑第i件物品的策略(放或不妨),那么就可以转化为一只牵扯前i-1件物品的问题。如果不放第i件物品,那么问题就转化为“前i-1件物品放入剩下的容量为v-c[i]的背包中”,此时能获得的最大价值就是f[i-1][v-c[i]]再加上通过放入i件物品获得的价值w[i].

初始化细节问题

看到的求最优解的背包问题题目中,有两种不太相同的问法。有的题目要求“恰好装满背包”时 的最优解,有的 题目则并没有要求把背包装满。一种区别就是这两种问法的红丝线方法在初始化时候有点不同。

   如果说第一种问法,要求恰好装满背包,那么在初始化的时候除了f[0]为0其他f【1.v】均为-∞,这样就可以保证最终的得到的f[N]是一种恰好装满背包的最优解。如果并没有要求把背包装满,只是希望价格尽量大,初始化时应该将f[o..v]全部设为0.

可以这样理解:初始化的f数组事实上就是在没有任何物品可以放入背包时的合法状态。如果要求背包恰好装满,那么此时只有容量为0的背包可能被价值为0的nothing“恰好装满”,其他容量的背包均没有合法的解,鼠疫未定义的状态,他们的值就应该是-∞,如果背包并非必须装满,那么任何容量的背包都有一个合法解”什么都不装”,这个解额度价值为0,所以出事时状态的值也就全部为0了。这个技巧可以推广到其他类型的背包问题,后面也就不再对进行状态转移前的初始化进行详解。

定义区间DP:

区间动态规划问题都是要考虑的,他们的最优值都是由极端更小的区间的最优值得到的,是分制思想的一种,将一个区间问题不断划分为更小的区间直到一个元素组成的区间枚举他们的组合,求合并后的最优值。

设F[i,j]=0

每次用变量k(i<=k<=j-1)将区间分为[i,k]和[k+1,j]两段

For p:=1 to n do//p是区间长度,作为阶段。

For i:=1 to n do//i是穷举的区间的起点

Begin

J:i+p-1 ;// j是区间的终点,这样所有的区间就穷举完毕

If j>n then break;//这个if很关键。

For k:=i to j-1 do//状态转移,去推出f[i,j]

F[i,j]=max{f[i,k]+f[k+1,j]+w[i,j]}

End;

 

以上就是我的acm总结。

0 0
原创粉丝点击