弗洛伊德算法是解决多元最短路径的算法(什么是多源, 顾名思义就是起点有多个, 跑完floyd算法就算出以每个顶点做起点到各个点的最短路径)。
1、多源最短路
2、带负权值的
优点:容易理解,可以算出任意两个节点之间的最短距离,代码编写简单。
缺点:时间复杂度比较高,不适合计算大量数据。
flody算法是从中间结点的角度来考虑的
for(k = 0;k < n;k++)
for(i = 0;i< n;i++)
for(j = 0;j <n;j++)
if(grap[i][k]!=inf&&grap[k][j]&&grap[i][j] > grap[i][k]+grap[k][j])
grap[i][j] = grap[i][k]+grap[k][j];
floyd算法用到的是动态规划算法。
动规公式: grap[i][j] = min(gtap[i][j], grap[i][k]+grap[k][j]).
对于floyd的证明:
floyd的证明
为优化的时间复杂度:外循环n-1次, 内循环 2*n次 , 所以时间复杂度 O((n-1)(2*n))近似 O(n^2)
1、单源最短路径
2、不带负权值的边
对于dijkstra我一般喜欢用邻接矩阵, 邻接表储存图
/**********
原理
每次 通过 未使用的点中,距起点最近的那个点 去松弛其他点的距离 (其实就 用近点(从最近开始, 然后是第二近...) 松弛远的点, 本质就是贪心思想)
所以dijkstra 时间复杂度主要跟点的个数有关
***********/
#include
#define inf 0x3f3f3f
using namespace std;
int G[1111][1111];// 邻接矩阵 , 也可以用邻接表
int n; //图中点的个数
void Dijkstra(int s, int d)// s 起点 , 终点
{
int i;
int dis[1111];// 存s到 其他各点的距离
/**初始化距离**/
for(i = 1;i <= n;i++) dis[i] = inf;// 到其他点的距离为无限大
dis[s] = 0; //初始化到自己为1
/***标记数组初始化****/
int book[1111];//标记数组,标记该点是否使用
memset(book, 0 , sizeof(book));//初始化 0表示未使用
for(i = 1;i <= n-1;i++)// 循环 n-1 次, 最远的那个点(就是距离 起点 第n近的点(这里第1近的点是起点自己)肯定不能松弛其他店)
{
/**********找点过程**********/
int u = -1;//-1 u表示未使用的点中,距起点最近的那个点,初始化z最近点的编号为 -1
int Min = inf;// 初始化 未使用的点中,距起点最近的那个点到起点的距离为 inf
for(int j = 1;j <= n;j++)
{
if(book[j] == 0&& dis[j] < Min)//寻找未使用的点中,距起点最近的那个顶点
{
Min = dis[j];
u = j;//储存该顶点
}
}
if(u == -1)break;//没找到 可以提前结束
book[u] = 1;//标记该点使用
/***********通过 u点松弛其他点的过程*********/
for(int j = 1;j <= n;j++)
{
if(G[u][j] < inf)//查找该点相连接的点(查看改点有哪些出边)
{
if(dis[j]> dis[u]+G[u][j])//判断源点是否能通过点u 与点j缩短距离(松弛操作)
{
dis[j] = dis[u]+G[u][j];
}
}
}
}
printf("%d\n", dis[d]);
}
#include
#include
#define inf 0x3f3f3f
int G[1111][1111];
int n; //点 的个数
void Dijkstra(int s, int d)
{
int i;
/***初始化起点到其他点的距离***/
int dis[1111];//表示 起点s 到其他点的距离
for(i = 1; i <= n; i++) dis[i] = G[s][i]; //将邻接矩阵的值赋给 dis[]
dis[s] = 0;
/**初始化 标记数组***/
int book[1111];
memset(book, 0, sizeof(book)); //标记数组
book[s] = 1; //标记自己
for(i = 1; i <= n-2; i++) // 因为 在初始化距离是 起点s已经被使用了 所以循环 n-2次就行了
{
int u = -1;//-1 初始化 未被使用的到起点最近的点
int min = inf;// 初始化距离
/****找点过程****/
for(int j = 1; j <= n; j++)
{
if(book[j] == 0&& dis[j] < min)//寻找离源点最近且未被标记的顶点
{
min = dis[j];
u = j;//储存该顶点
}
}
if(u == -1)break;
book[u] = 1;//标记该点
/********通过u点松弛过程*********/
for(int j = 1; j <= n; j++)
{
if(G[u][j] < inf)//查找该点相连接的点(查看改点有哪些出边)
{
if(dis[j]> dis[u]+G[u][j])//判断源点是否能通过点u 与点j缩短距离(松弛操作)
{
dis[j] = dis[u]+G[u][j];
}
}
}
}
printf("%d\n", dis[d]);
}
对于不理解为什么 最外层for 循环多少次的 可以改成while(1): 通过if(u == -1) break;结束循环。
注释:u == -1表示 找不到未被使用的 到起点最近的 点(就是当前未被使用点的集合中 找不到到起点距离最小的点)。
时间复杂度O((m+n)logn);// n点的个数 m边的个数
/*********
优化部分:通过小顶堆,优化了 对于 被使用的点中,距起点最近的点 的查找。
************/
#include
using namespace std;
const int INF = 0x3f3f3f;
struct Edge
{
int v;
int w;
};
int n, m;
vector <Edge> G[5000];//vector形式的邻接表
typedef pair<int, int> P;//第二个值表示点, 第一个值表示 从起点到该点的距离
void Dijkstra(int s, int e)
{
priority_queue<P, vector<P>, greater<P> > que;//按照greater排序, 实现小顶堆
while(!que.empty())que.pop(); //初始化堆
/******初始化距离********/
int dis[511111];
fill(dis,dis+n+1, INF);//初始化
dis[s] = 0; //到自己的距离 为 0;
/*********先将自己(自己是距离起点最近的点)压入优先队列中***********/
que.push(P(0,s));
while(!que.empty())//直到队列为空才结束
{
P temp = que.top();//顶部赋给 temp
que.pop();//出队
int u = temp.second;// 表示堆中距离起点最近的点
if(temp.first > dis[u])continue; // 该操作 相当于 标记点u 是否被重复使用, 可以更换为标记数组
for(int i = 0; i < (int)G[u].size(); i++) //通过该顶点u(u的边都循环一遍) 进行松弛操作
{
Edge e = G[u][i];
if(dis[e.v] > dis[u] + e.w)
{
dis[e.v] = dis[u]+e.w;
que.push(P(dis[e.v], e.v));
}
}
}
cout<<dis[e]<<endl;
}
时间复杂度:n*m; // n节点数 m 边数
/********
原理
ford是通过 路(准确的说是迹)的路长 进行松弛的。(第一次是 找路径为1的路 松弛对应终点 ,在找路径为2的路 松弛 )
过程 从路长从小到大 松弛 每个点。
所以 ford 算法 跟边有关
***********/
#include
using namespace std;
const INF = 0x3f3f3f;
const maxn = 1e5;
struct ENode
{
int f;
int to;
int w;
} E[maxn];
int Ford(int s)
{
int flag = 0; // 标记 有没有 负边
int dis[maxn];
memset(dis, INF, sizeof(dis));
dis[s] = 0;
// n是图中 点的个数, m是图中边的个数
/********通过路长松弛******/
for(int i = 1; i <= n-1; i++) // 循环 n-1次,原因 起点到每个点的最短路 都是迹 迹的长度最大为n-1, 所以循环n-1次
{
for(int j = 1; j <= m; j++) // 循环m个边
{
int u = E[j].f; int v = E[j].t; int w = E[j].w;
if(dis[v] > dis[u]+ w)
{
dis[v] = dis[u]+w;
}
}
}
/**********判断是否有负环***********************/
for(int j = 1; j <= m; j++) //再循环一次边
{
int u = E[j].f; int v = E[j].t; int w = E[j].w;
if(dis[j] > dis[u]+w)//如果dis 松弛还能变小,就有负边
{
flag = 1;
break;
}
}
return flag;
}
复杂度: o(e); e表示边数
/***********
ford算法 是通过最短路的路长 来松弛的路的终点, 每次循环所有的边 每次循环是 不是每个边都被用上
所以spfa 就通过队列来优化 有点像 bfs的涟漪效应。
通过队列将路径为1的压入队列, 然后在利用路径为1的路 的终点,利用该终点的边 ,来松弛路径为2的路对应的终点。依次类推。
*************/
#include
#include
#include
#include
#include
using namespace std;
const int INF = 0x3f3f3f;
const int maxn = 1e5;
struct ENode
{
int f;
int to;
int w;
};
vector<ENode>E[maxn];//邻接表 , 或者叫 vector+ 前向星, 或者叫链式前向星。
int dis[maxn];//记录距离的数组
bool spfa(int s, int n)
{
int cnt[maxn];//统计入队次数, 判断 是否存在负环
memset(cnt, 0, sizeof(cnt));
memset(dis, INF, sizeof(dis));//初始化
dis[s] = 0;
bool vis[maxn];//标记数组
memset(vis, false, sizeof(false));//初始化
queue<int>que;//定义一个队列
que.push(s);//将起点压入
vis[s] = true;//标记
cnt[s]++;
while(!que.empty())
{
int u = que.front();
que.pop();
vis[u] = false;
for(int i = 0;i < (int)E[u].size();i++)
{
int v = E[u][i].to;
if(dis[v] > dis[u]+ E[u][i].w)
{
dis[v] = dis[u]+E[u][i].w;
if(!vis[v])
{
vis[v] = true;
que.push(v);
if(++cnt[v] == n)return false;// 通过每个点入栈的次数判断是否存在负环路; 本质就是图中的最短路的 路长不大于n-1 ,如果大于n-1就有负环 路导致。
}
}
}
}
return true;
}