Floyd
Floyd
-
本质:DP
-
存储结构:邻接矩阵,若有重边则读入时必须取最小边
-
算法特点:多源最短路,能一次性求解所有点对间的最短距离
-
适用对象:小图,允许负权图,无法适用于负环图(负环:环上边权之和为负的环,当任意时刻出现 d p [ i ] [ i ] < 0 dp[i][i]<0 dp[i][i]<0时则有负环)
-
核心思想:考查每个点作为中转点,尝试每一点对距离是否能通过中转点被松弛
-
算法流程:闫氏DP分析法
-
状态表示:
- 集合:设 i i i为当前轮源点, j j j为当前轮终点, k k k为当前轮中转点。定义状态矩阵 d p k [ i ] [ j ] dp_k[i][j] dpk[i][j],表示在 i i i到 j j j的路径上,所经过的顶点编号 ≤ k \le k ≤k的最短路长度(初始化为 + ∞ +\infty +∞表示该点不可达,主对角线初始化为 0 0 0)。路径矩阵 p a t h k [ i ] [ j ] path_k[i][j] pathk[i][j]表示考查第 k k k个顶点之后, j j j点的上一个来源顶点(初始化为 − 1 -1 −1表示无路径)。
- 属性: M i n Min Min
- 初始化:初始状态即为原邻接矩阵
-
状态计算: d p k [ i ] [ j ] dp_k[i][j] dpk[i][j]:对于第 k k k个顶点,此处确保第 k k k个点一定可选(与其他dp问题不同)
- 不选第 k k k个点:若选择第 k k k个点作为中转点无法对 i i i到 j j j路径进行松弛(无法保证最优),则不选。路径状态仍为 [ i , j ] [i,j] [i,j],直接继承自 k − 1 k-1 k−1个顶点时 i i i到 j j j的路径。 d p k [ i ] [ j ] = d p k − 1 [ i ] [ j ] dp_k[i][j]=dp_{k-1}[i][j] dpk[i][j]=dpk−1[i][j]。
- 选第 k k k个点:选择第 k k k个点作为中转点会使 i i i到 j j j的路径松弛,则选第 k k k个点。路径状态变更为 [ i , k ] , [ k , j ] [i,k],[k,j] [i,k],[k,j],继承自 k − 1 k-1 k−1个顶点时 i i i到 k k k, k k k到 j j j的路径,并相加。 d p k [ i ] [ j ] = d p k − 1 [ i ] [ k ] + d p k − 1 [ k ] [ j ] dp_k[i][j]=dp_{k-1}[i][k]+dp_{k-1}[k][j] dpk[i][j]=dpk−1[i][k]+dpk−1[k][j]。
-
状态转移方程式: d p k [ i ] [ j ] = m i n ( d p k − 1 [ i ] [ j ] , d p k − 1 [ i ] [ k ] + d p k − 1 [ k ] [ j ] ) dp_k[i][j]=min(dp_{k-1}[i][j],dp_{k-1}[i][k]+dp_{k-1}[k][j]) dpk[i][j]=min(dpk−1[i][j],dpk−1[i][k]+dpk−1[k][j])。
-
-
复杂度: O ( V 3 ) O(V^3) O(V3)
Floyd在大多数情形下必须使用自我滚动将空间复杂度降低到 O ( V 2 ) O(V^2) O(V2)才能使用。下面仍给出朴素版供对比。
朴素版
using ll=long long;
const ll INF=__LONG_LONG_MAX__;
extern ll dp[MAXV][MAXV][MAXV],path[MAXV][MAXV][MAXV];//dp[0][MAXV][MAXV]已默认存储图的边权数据的最小值
extern ll n;//点数
void floyd(){
for(ll k=0;k<n;k++){
for(ll i=0;i<n;i++){
for(ll j=0;j<n;j++){
if(dp[k-1][i][k]!=INF&&dp[k-1][k][j]!=INF&&dp[k-1][i][j]>dp[k-1][i][k]+dp[k-1][k][j]){//松弛操作,特判!=INF是为了防止数据溢出
dp[k][i][j]=dp[k-1][i][k]+dp[k-1][k][j];
path[k][i][j]=path[k-1][k][j];
}
else{
dp[k][i][j]=dp[k-1][i][j];
path[k][i][j]=path[k-1][i][j];
}
}
}
}
}
滚动数组优化
交替滚动
using ll=long long;
const ll INF=__LONG_LONG_MAX__;
extern ll dp[2][MAXV][MAXV],path[2][MAXV][MAXV];//dp[0][MAXV][MAXV]已默认存储图的边权数据的最小值
extern ll n;//点数
void floyd(){
ll work=0,old=1;
for(ll k=0;k<n;k++){
swap(work,old);
for(ll i=0;i<n;i++){
for(ll j=0;j<n;j++){
if(dp[old][i][k]!=INF&&dp[old][k][j]!=INF&&dp[old][i][j]>dp[old][i][k]+dp[old][k][j]){//松弛操作,特判!=INF是为了防止数据溢出
dp[work][i][j]=dp[old][i][k]+dp[old][k][j];
path[work][i][j]=path[old][k][j];
}
else{
dp[work][i][j]=dp[old][i][j];
path[work][i][j]=path[old][i][j];
}
}
}
}
}
自我滚动
using ll=long long;
const ll INF=__LONG_LONG_MAX__;
extern ll dp[MAXV][MAXV],path[MAXV][MAXV];//dp[MAXV][MAXV]已默认存储图的边权数据的最小值
extern ll n;//点数
void floyd(){
for(ll k=0;k<n;k++){//依次考查每个点作为中转点
for(ll i=0;i<n;i++){//依次考查每个点作为源点
//if(dp[i][k]!=INF&&i!=k) //此处为小优化,提前判断,若常规写法T掉可尝试
for(ll j=0;j<n;j++){//依次考查每个点作为终点
if(dp[i][k]!=INF&&dp[k][j]!=INF&&dp[i][j]>dp[i][k]+dp[k][j]){
//松弛的3个条件:中转点dis非无穷,中转点到终点dis非无穷,通过中转点使源点到终点距离更短
dp[i][j]=dp[i][k]+dp[k][j];
path[i][j]=path[k][j];
}
}
}
}
}
路径输出
void print(ll s,ll t){
if(s==t){
cout<<s<<' ';
return;
}
print(s,path[s][t]);
cout<<t<<' ';
}
应用:传递闭包
传递闭包:求所有元素间的传递连通关系
算法流程:定义状态数组 d p [ i ] [ j ] dp[i][j] dp[i][j],表示 i i i与 j j j的连通性(直接/间接到达)。当 i i i与 j j j连通时, d p [ i ] [ j ] = 1 dp[i][j]=1 dp[i][j]=1;否则 d p [ i ] [ j ] = 0 dp[i][j]=0 dp[i][j]=0
朴素版( O ( V 3 ) O(V^3) O(V3))
void floyd(){
for(int k=0;k<n;k++)
for(int i=0;i<n;i++)
//if(dp[i][k])//小优化,提前判断,若T掉可尝试
for(int j=0;j<n;j++)
if(dp[i][k]&&dp[k][j])
dp[i][j]=1;//6-7行可合并为dp[i][j]|=dp[i][k]&dp[k][j]
}
bitset优化( O ( V 2 ) O(V^2) O(V2))
bitset<N>d[N];
void floyd(){
for(int k=0;k<n;k++)
for(int i=0;i<n;i++)
if(d[i][k]) d[i]|=d[k];
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具