欢迎使用CSDN-markdown编辑器

图论报告

浅谈最短路

标签(空格分隔): 算法报告 图论 最短路


内容

  1. 最短路
    • Floyd v3
    • Bellman-ford ve
    • Dijkstra v2
    • Dijkstra Heap优化 vlogv+elogv
    • SFPA ve
    • A*
  2. 所有节点对最短路
    • 矩阵乘法 v4
    • Floyd-Warshall v3
    • Johnson * (可于负权图) v2logv
  3. 最小生成树
    • Prim v2 (邻接表 elogv
    • Kruskal eloge
  4. 具体题目

最短路大家族

Floyd ——正负权均可

这是一个耿直的,但是最容易写的代码。
本质是dp
对于只使用前 k 个点,求得最优解
那么就可以dp出 使用前 k + 1 个点的最优解
以下是dp过程

for(int k=1; k<=n; k++)   
    for(int i=1; i<=n; i++)   
        for(int j=1; j<=n; j++)   
            if(e[i][j] > e[i][k] + e[k][j])   
                e[i][j] = e[i][k] + e[k][j]; 

Bellman-ford ——正负权均可

Bellman-ford是 不断松弛 寻得最优的过程
每次对所有边松弛操作一次
可以证明,至多 v-1 次,即可求得解

for(int i=1; i<=v-1; i++)
    for(int j=1; j<=e; j++){
        int u = edge[j].u;
        int v = edge[j].v;
        int weight = edge[j].weight;
        if(dis[v] > dis[u] + weight)
            dis[v] = dis[u] + weight;
    }
// 判断是否存在负环
bool flag = 0;
for(int i = 1; i<=e; i++){
    if(dis[edge[i].v] > dis[edge[i].u] + edge[i].weight){
        flag = 1;
        break;
    }
}
// flag == 1 存在负环

Dijkstra —— 无法负权

Dij是每次通过获得最快能到达点,在以此继续贪心,来得到最短路的算法。

对于如下 n = 3 的邻接矩阵

0 2 3
2 0 -2
3 -2 0

在更新的过程中 Dis[i]的状态如下

步骤 dis1 dis2 dis[3] 过程
0 0 从点1触发
1 0 2 3 从点2触发
2 0 2 1 完毕

那么,dis2通过Dij得出的结果是 2
但是,很明显,可以通过 1->3->2的方式得到一个 1 的更优解

为什么Dij会失败呢?

Dij 是贪心,每次贪心得出(最快能到达的点)
负权会产生影响,把之前得到的某个点再次优化
但是这个被影响的点已经跑完了,就不会更新他所能影响的点

Dijkstra —— Heap 优化

Dij的算法步骤如下

  • 在未纳入的点中,获得最优点①
  • 以该点出发,更新其他点 ②

步骤① 会重复执行 v
步骤② 执行的总次数实际上所有的边 即 e
而步骤 ① 需要 获得最优点,朴素的搜索需要 v
步骤② 的更新朴素实现是 O(1)
所以总时间是 vv+e1

Dij的Heap优化在于,把获得最优点更新其他点用堆来实现
获得最优点即是删除堆顶 复杂度为 logV
更新其他点则是修改堆的节点值 复杂度为 logV
所以 Heap 优化后的Dij,时间为 vlogv+elogv
即复杂度为 O(elogv)

实际上,当 e=v2 时 Dij的Heap会退化成一个 O(v2logv)
效率会低于朴素的Dij的实现( O(v2) )

PS:
鉴于 Dij 的实际操作是 降权删除堆顶
使用斐波那契堆 可以让降权操作的摊还复杂度为 O(1)
斐波那契堆实现的Dij的复杂度为 O(e+vlogv)
然而实际上并没有哪个需要用它,毕竟卡SPFA的不会卡heap,而SPFA大法可以解决大部分问题……

SPFA (正负权)

Shortest Path Faster Algorithm
吐槽:一般的算法名都是由作者名字命名,然而这个命名则是中文的,尤其是这个Faster,显得很是中二……

这是一个最坏能达到 O(ve) 的算法
其为 Bellman-ford 的优化
因为 Bellman-ford 是一个不断松弛的算法,但松弛过程中有很多无效操作
SPFA则把每次被更新的点重新纳入计算,以此减少无效步骤

SPFA的具体做法是维护一个顶点的队列
不断地从头到尾扫描队列
如果队列中某个点的标记值为true
则以此出发更新其他点,被更新到更优解的点,标记true
该点标记false
初始只有起点为true

SPFA 是一种 XJBS 出奇迹的典型……
虽然它的最劣很劣,但通常情况是快于甚至远远快于其他算法的
——有不靠谱的书认为SFPA是一个 O(ke) 的算法(k为常数)

举个SPFA最劣情况的例子
n = 5 的邻接矩阵

矩阵 点1 点2 点3 点4 点5
点1 0 16 16 16 1
点2 16 0 1 4 8
点3 16 1 0 1 8
点4 16 4 1 0 1
点5 1 8 8 1 0

第一遍遍历队列的过程
点1 会把 2 3 4 5全部更新
此时情况是

Dis 点1 点2 点3 点4 点5
距离 0 16 16 16 1

(之后的过程中,2,3,4点的更新是无效的)
而到达5后,会更新为

Dis 点1 点2 点3 点4 点5
距离 0 9 9 2 1

点2、3、4会重新
下一次,会继续对2、3、4点更新
之后点4又再次更了2、3
这种情况下,会更新V轮
每轮为E次更新
则达到最坏复杂度

A* (启发式搜索)

DFS和BFS都是 地毯式 搜索
A* 是搜索的时候,带一个 估价函数 试图往更好地方向上搜

S 为源点
T 为汇点
对于点
我们记录 F(a,b) 为从 a 到 b 的 实际 最短路程
我们记录 P(a,b) 为从 a 到 b 的 估价 最短路

BFS 是 每次求得最小的一个 F(S,a) 的过程
那么,如果我对当前搜得的点,
估算一个 F(S,a)+P(a,T) 如果 P(a,T)=F(a,T)
他实际就是 S 经由 a 到达 T 的最短路
但是 这个 P(a,T) 我无法直接求得

可以证明
如果 P(a,b)<F(a,b) 将可以求得最短路
如果 P(a,b)=F(a,b) 将等效于 BFS
如果 P(a,b)>F(a,b) 将较快搜得结果,但不一定是最短路


所有最短结点对

Floyd

Floyd是可以在 V3 内实现求得所有最短结点对的

对每个点求一次最短路

如果对每个点使用一次SPFA,可以求得结果,但效率不会很稳定。
(鉴于SPFA的最坏是 VE ,但实际上似乎对每个点搜索,不会都达到最劣)
如果对每个点,跑一次 Dij
其复杂度为 v3
对每个点跑一次Dij的heap优化
如果使用斐波那契堆,可以达到单次搜索 vlogv
整体复杂度 vvlogv
而Dij对负权无能为力
Johnson就是一个对含负权的图作出处理
而后用Dij堆优化来跑得结果的算法

Johnson

神奇的SPFA,是会被偶尔卡一下的
而堆优化的dij,仍然会死于负权

Johnson就是一个处理带负权的图,让它能跑dij堆来处理所有结点对最短距离的方法。

如果,我们在每个节点,赋予一个权值 h[i]
记原i、j两点的边权为 w[i][j]
新的边权 记为 W[i][j]=w[i][j]+h[i]h[j]
所寻得的所有最短路,是否与原来一致?

我们记
path[i] 为 原来从源点到 i 点 实际最短路
Path[i] 为 后来从源点到 i 点 实际最短路

我们知道
Path[i]=W[1][v1]+W[v1][v2]+...+W[vn][i]
W[i][j]=w[i][j]+h[i]h[j]
所以
Path[i]=w[1][v1]+w[v1][v2]+...+w[vn][i]+h[1]h[v1]+h[v1]h[v2]+...+h[vn]h[i]

Path[i]=w[1][v1]+w[v1][v2]+...+w[vn][i]+h[1]h[i]
h[1]h[i] 是固定的
所以,当 w[1][v1]+w[v1][v2]+...+w[vn][i] 取最优时
Path[i] 取最优
path[i] 即是最优选项

所以,合理构造出 h[i] 就可以构造出一张无负权的图

这个合理,可以是
新建一个点 T0 这个点到所有点,连一条权值为0的单向边。
首先对这个点跑一次最短路 SPFA 或 其他
然后把跑出来的值,赋值给每个点

因为 跑完后另 h[i]=d[i]
( d[i] 为从 T0 到 i 的实际最短路)
d[j]d[i]+w[i][j]
我们要使得 W[i][j]=w[i][j]+h[i]h[j]0 的目的 也就实现了

PS:跑出负环说明原图有负环

题目


0.引子:渡河

最短路是一个实用性很强,应用面很广的算法。

一个经典的问题
人、狼、羊、白菜 要过河
每次渡河 需要人来
但是人不在的时候
狼要吃羊
羊要啃白菜

如何用最少的步骤,安全渡河,没有谁受伤害……


*如果人渡河要花力气,如运白菜要1力气,运羊要2力气什么的,怎样最省力呢?
如果不止有人狼羊白菜,还有喜羊羊灰太狼灰太狼呢?*


我是邪恶的分割线


我们把所有情况罗列
如 人 羊 表示 人和羊在此岸,狼和白菜在彼岸
把每种情况看做一个节点
然后,把不能存在的情况剔除(狼爱上羊,好白菜被羊拱了什么的)

那么,每次人渡河后,会从某情况,变成另一种情况
于是在两种情况间加边(权值为1)

于是问题转变为了
找 从 人狼羊白菜(空) 的最短路

1.从简单的热身题开始

一个 MN 的网络,每个节点有一个高度
有一个初始速度 S
每次可以上下左右移动
移动花费的时间是1/S
每次移动后,速度会变化成 S2H1H2
(H1、H2 分别是两点的高度)

求从左上到右下的最短时间


我是邪恶的分割线


2H1H22H2H3=2H1H3

2.带限制条件的最短路

双向图,无负权
每条边有长度L和花费K两个值
你有一定的钱数
用给定的钱数,走一条最短路。

N100
L,K10000


我是邪恶的分割线


很朴素的做法是
用dij的heap做,
每次只要到某点的花费 K,就把他放进heap里
同时,仍然是把距离最短的作为堆顶
这样弹弹弹,第一个弹出的目的点,必然是距离最短且花费符合的。

3.变形:字符串变化

World Finals 2009

给出n个字符串后缀替换规则,每个由长度L不超过20的两个等长字符串A,B表示,表示若当前字符串以A为后缀,可以将其替换为B。现给出等长串S和T,问能否替换,最少操作多少次?

例,给出规则如下
1.A - B
2.AB - BA
3.AA - CC
4.CC - BB
S : AA
T : BB
可以 AA - AB(1) - BA(2) - BB(1)
也可以 AA - CC(3) - BB(4)

n <= 100


我是邪恶的分割线


首先,如果搜索是会GG的,因为状态很多,会炸。

我们假设,给出的所有规则,长度一样
那么,是不是就变得和之前的题目0一样,可以连边确定最短路

同时,长度为 L 的状态涉及的变化,是与大于L的长度的状态无关的

再看题目,对于给定长度L
我们必然是 从长度L的初始态,经过某些变化,经过了某些长度L的中间态(也可能没有)(PS:这里的中间态,指的是题目中给出的规则中出现的状态),变为了长度L的最终态

且,两种长度L的相邻的状态(存在于给定规则中),其变化必然不涉及其他的给定中间态。只涉及长度 <L 的状态。
而两种

所以,只需求出任意两种长度L的状态之间的最短变化距离即可

那么,依次递归,求出某长度下状态间的最短路。

说的有点乱,举个栗子

对于 AAAA - DDDD
如果是按下面的顺序变完
AAA - BBB
BB - CC
BCC - BCD
ABCD - PDDP
DDP - DDD
PDDD - DDDD
这样的规则
对于长度为 4 的串 实际中出现了很多
但 我们要求的只是 AAAA ABCD PDDP PDDD DDDD 之间的距离
在假定递归完成的情况的
我们知道了 所有出现在规则中的长度相同的串之间的最短距离
首先 从 A*** 转化到 P*** 这样的转化,必然是需要一个“出现在规则中的”长度为4的串
而在A*** 和 A***之间的转化会涉及到长度小于4的串
这个时候,已经与 A 无关,纯粹的是 * 与 * 之间的转化
* 和 * 的出现次数有有限的,就是规则中出现的。

所以
1、把所有的长度为 1 的规则 计算 转化距离
如 A - B A - K (因为 n = 100, 所以最多 200 点)

2、对于长度为2的所有出现的规则(包括后缀)
如果有直接变化规则,加一条权值为1的边
否则,如果是A* - A*的形式,加 * - *的最短路(已经求出)
求最短路

3、对于长度为3的所有出现的规则
如果有直接变化规则,加一条权值为1的边
否则,如果是A** - A** 的形式,求 * - * 的最短路即可
(AA* - AA* 的形式,包含在内)

结点对距离直接用Floyd的话,复杂度为 O(Lv3) ,可以接受
如果用SPFA的话会更快。


4.套路题:逃亡的小动物

一个 NM 的网格图,且每个格子的左上与右下相连,如下
边有权重
欢迎使用CSDN-markdown编辑器_第1张图片
小动物从左上向右下逃亡,问最少多少人手可以阻止小动物成功逃亡。

n,m1000
权值 106
PS:不许跑网络流


我是邪恶的分割线


套路之平面图最小割

首先这是一个平面图

在图论中,平面图是可以画在平面上并且使得不同的边可以互不交叠的图。而如果一个图无论怎样都无法画在平面上,并使得不同的边互不交叠,那么这样的图不是平面图,或者称为非平面图。

建立其对偶图

假设G=

5. 套路题:牛排队

n只牛排队,从1到n,按编号顺序
同时有一些限制:
1、x 牛 y 牛 距离不能超过 maxd
2、x 牛 y 牛 距离不得小于 mind
求方案使得牛排的队最长


我是邪恶的分割线


套路之差分约束系统
转化为最短路做
有两种情况
不能超过 dydx+maxd
不得小于 dxdymind

分别是添加单向边
x 向 y maxd
y 向 x - mind

还有默认的 dxd(x+1)+0
x+1 向 x 添加 0 的单向边

然后跑一发 1 到 n 的最短路

  • 负环 无解
  • 无路 无穷大
  • 有解 即为解

为什么这样就能解决这个问题呢?

我们看最短路的一个描述
d[v]d[u]+w[u,v]
d[i] 表示 源点到 i 点 的 实际最短路
w[i,j] 表示 i 到 j 的距离
最短路是不是就是这玩意儿?

那么,符合这描述的,是不是就是最短路?
好神奇也……



参考内容
1.百度百科
2.https://wenku.baidu.com/view/6b90bc59ad02de80d4d840e6.html
3.ACM国际大学生程序设计竞赛 题目与解读(俞勇主编)

你可能感兴趣的:(算法_搜索)