第九章
- 动态规划的基本思路:
- 找到问题的最优子结构
- 确定状态
- 写出状态转移方程。
注: 如果发现状态无法转移,我们常考虑增加状态的维度。
使用动态规划的动机: 1. 问题是线性的,容易找到最优子结构。 2. 问题是一个多阶段决策问题。
- 动态规划中解答路径的输出
- 不记录,求解完所有状态后,通过状态转移方程递推求出每一步的决策。(注意使用记忆化搜索DP时,打印方案也需要使用函数而不是数组)
- 记录每一步的决策。(LCS问题不好处理)
- 记录方案,每次更新全局最优解时同步跟新方案。
- 动态规划的递推方向:
- 向前递推可以边输入边递推。但是较难按字典序输出方案
第十一章
首先总结一下图论中的一些概念:
连通: 如果在一个无向图中从每一个顶点到每个其他顶点都存在一条路径,则称该无向图是连通的,具有这样性质的有向图称为是强连通的。如果一个有向图不是强连通的,但是它的基础图,即其边上去掉方向所形成的图,是连通的,那么该有向图称为是弱连通的。
完全图是其每一对顶点都存在边的图。
双联通性: 如果一个连通的无向图中的任一顶点删除之后,剩下的图仍然连通,那么这样的无向连通图就称为是双连通的,删除后图不再连通的顶点叫割点。
最小生成树(MST)问题
Kruskal: 每次从剩余边集合中选一个不和现有边构成环最小边(使用并查集)。O(ElogV)
Prim: 从一个顶点出发,从已选点集和未选点各一个作为顶点所构成的边中找一个最小的,然后将该边的未选点集的顶点置入已选点集, O(N^2)。
最短路问题
顺便贴一下ACM中常用的邻接表表示代码(对边进行编号,利用数组建立链表):
邻接表
int n, m; // n顶点数, m边数
int first[MAXN];
int u[], v[], w[], next[];
void read_graph()
{
scanf("%d%d", &n, &m);
for(int i = 0; i < n; i++)first[i] = -1; //初始化表头
for(int e = 0; e < m; e++)
{
scanf("%d%d%d", &u[e], &v[e], &w[e]);
next[e] = first[u[e]]; //在链表头插入
first[u[e]] = e;
}
}
稠密图 : m和n^2一个量级; 稀疏图 m 和 n 一个量级。
- Dijkstra:单源最短路径经典算法,边权值需要全部为正。效率为O(n^2),使用优先队列后为O(mlogn)。Dijkstra算法要求松弛操作具有累积递增性(贪心性质),具体分析下在普通情况下该性质,当前未标记的d[k..s]中的最小一个d[i]是不能再被松弛了,因为其他未标记的d[s]都比他大,若以s为起点进行松弛操作只能使结果比d[s]更大。因此d[i]就已经是最小值,可以将其标记。推广到一般,只要图中松弛操作满足这个性质,就可以使用Dijstra算法思想来解决问题.当图中含有负权时,显然这个性质不能满足,所以算法失效。
- Bellman-Ford算法 :单源最短路径,允许有负权值边。若存在负环可以通过迭代一定次数后通过收敛性判断进行检测。简单算法效率为O(VE)。其基本思想为:
长度为k的路由长度为k-1的路加一条边得到;
由此我们可以得到只需依次考虑长度最多为1,2,…,k-1的最短路作为最优子结构进行求解。
使用队列的版本即为SPFA(Shortest Path Faster Algorithm)算法,时间效率为O(kE), 具体可以参看这篇文章
-
SPFA判断负环的方法,是看是否有点入队次数超过|V|次,其思想为若负环不存在,每个点最多被松弛 |V-1|次,可以参考非队列版本,只有该点在求边长为1,2,3,...,|V|-1的路径时都被松弛了,才可能达到这个极限,因此最多入队|V|-1次,由于原点要多入队一次,因此上限取|V|可行。
- Floyd-Warshall 算法,用于求解任意两点间的最短距离,时间复杂度为O(n^3),空间复杂度为O(n^2), 也是一个动态规划算法。对稠密图效果比使用n次dijkstra算法效率好。要注意的是在它的三层循环中,K必须在最外层。

浙公网安备 33010602011771号