最短路
一.任意两点间的最短路径(Floyd)
1.
借用了动归的思想,d[i,j]保存i到j 的最短路长度
动态转移方程:d[ i , j ]=min(d[ i , j ],d[ i, k ]+d[ k , j ])
void floyd(int n) {
for (int k = 1; k <= n; k++) {
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
dis[i][j] = Min(dis[i][j], dis[i][k]+dis[k][j]);
}
}
}
}
虽然floyd算法的效率比较低,但也有优点
(1)可以一次性求出所有结点之间的最短路径;
(2)能处理有负权边的图(但不能处理负圈)
判断负圈
在初始化的时候,如果没有把dis[i][i]为0的话,dis[i][i]=dis[i][u]+。。。+dis[v][i]就是在外面绕一圈回来的路径,借助这一点可以用来判断负圈;
负圈:环路上边的权值之和为0
只要在floyd中判断是否存在某个dis[i][i]<0就行了。
2.传递闭包
在交际网络中,给定若干个元素和若干个对二元关系,且关系具有传递性。“通过传递性推导出来尽量多的元素之间的惯性系”的问题称之为传递闭包。
d[i,j]=1表示i和j有关系,=0则表示没有关系。特别的d[i,i]始终为1;
#include<iostream>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<queue>
#include<stack>
#include<vector>
#include<cstdlib>
#include<cstdio>
using namespace std;
typedef long long ll;
const int maxn=101;
const int inf=0x3f3f3f3f;
int mp[maxn][maxn];
int u,v,w,n,m;
int main(){
scanf("%d %d",&n,&m);
memset(mp,0,sizeof(mp));
while(m--){
scanf("%d%d",&u,&v);
mp[u][v]=1;
}
for(int k=1;k<=n;k++){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
mp[i][j]=mp[i][j]||(mp[i][k]&&mp[k][j]);
}
}
}
int tot1=0;
for(int i=1;i<=n;i++){
int tot=0;
for(int j=1;j<=n;j++){
if(mp[i][j]||mp[j][i]) tot++;
}
if(tot==n-1) tot1++;
}
printf("%d\n",tot1);
}
二.单源最短路径
1.dijkstra算法
算法流程:(1)初始化dist[1]=0,其余节点的dist的值为无穷
(2)找出一个未被标记的、dist[x]最小的节点x,然后标记节点x
(3)扫描节点x所有的出边(x,y,z),若dist[y]>dist[x]+z,则使用dist[x]+z 更新dist[y]
(4)重复上午2~3两个步骤,直到所有节点被标记
优先队列写法
const int N = 1e5 + 10;
const int M = 1e6 + 10;
const int inf = 0x3f3f3f3f;
int head[N], ver[N], edge[M], Next[M], d[N];
bool v[N];
int n, m, tot;
priority_queue<pair<int, int>>q;
void add(int x, int y, int z) {
ver[++tot] = y, edge[tot] = z, Next[tot] = head[x], head[x] = tot;
}
void dijkstra() {
memset(d, inf, sizeof(d));
memset(v, 0, sizeof(v));
d[1] = 0;
q.push(make_pair(0, 1));
while (!q.empty()) {
int x = q.top().second;
q.pop();
if (v[x])continue;
v[x] = 1;
for (int i = head[x]; i; i = Next[i]) {
int y = ver[i], z = edge[i];
if (d[y] > d[x] + z) {
d[y] = d[x] + z;
q.push(make_pair(-d[y], y));
}
}
}
}
2.bellman-ford和SPFA算法
算法流程:
(1)建立一个队列,最初队列中只含有1
(2)取出对头节点x,扫描它的所有出边(x,y,z),若dist[y]>dist[x]+x,则使dist[x]+更新dist[y]。同时若y不在队列,则把y入队。
(3)重复上述操作,知道队列为空。
bellman-ford和SPFA算法可以在有负权的情况下正常工作,而dijkstra不行。
const int N = 1e5 + 10;
const int M = 1e6 + 10;
const int inf = 0x3f3f3f3f;
int head[N], ver[N], edge[M], Next[M], d[N];
bool v[N];
int n, m, tot;
queue<int>q;
void add(int x, int y, int z) {
ver[++tot] = y, edge[tot] = z, Next[tot] = head[x], head[x] = tot;
}
void spfa() {
memset(d, inf, sizeof(d));
memset(v, 0, sizeof(v));
d[1] = 0;
q.push(1);
while (!q.empty()) {
int x = q.front();
q.pop();
v[x] = 0;
for (int i = head[x]; i; i = Next[i]) {
int y = ver[i], z = edge[i];
if (d[y] > d[x] + z) {
d[y] = d[x] + z;
if (!v[y])q.push(y), v[y] = 1;
}
}
}
}