#include<bits/stdc++.h>
using namespace std;
#define MAX 0x3f3f3f3f
#define N 1010
int nodenum, edgenum, original; //点,边,起点
struct Edge //边
{
int u, v;
int cost;
};
Edge edge[N];
int dis[N], pre[N];//dis是起点到这个点的最短路,pre存路径
bool Bellman_Ford()
{
for(int i = 1; i <= nodenum; ++i) //初始化
dis[i] = (i == original ? 0 : MAX);
for(int i = 1; i <= nodenum - 1; ++i)//跑这么多次
for(int j = 1; j <= edgenum; ++j)//每次跑所有的边
if(dis[edge[j].v] > dis[edge[j].u] + edge[j].cost) //松弛(顺序一定不能反~)
{
dis[edge[j].v] = dis[edge[j].u] + edge[j].cost;
pre[edge[j].v] = edge[j].u;
}
bool flag = 1; //判断是否含有负权回路
for(int i = 1; i <= edgenum; ++i)//检验最短路中是否存在负权
if(dis[edge[i].v] > dis[edge[i].u] + edge[i].cost)
{
flag = 0;
break;
}
return flag;
}
void print_path(int root) //打印最短路的路径(反向)
{
while(root != pre[root]) //前驱
{
printf("%d-->", root);
root = pre[root];
}
if(root == pre[root])
printf("%d\n", root);
}
int main()
{
scanf("%d%d%d", &nodenum, &edgenum, &original);//点,边,起点
pre[original] = original;
for(int i = 1; i <= edgenum; ++i)
{
scanf("%d%d%d", &edge[i].u, &edge[i].v, &edge[i].cost);
}
if(Bellman_Ford())
for(int i = 0; i < nodenum; ++i) //起点到每个点最短路
{
printf("%d\n", dis[i]);
printf("Path:");
print_path(i);
}
else
printf("have negative circle\n");
return 0;
}
//Dijstra O(n^2)
//Dijkstra对于有负边权的最短路径不支持
void Dijstra()
{
int i,j;
for(i=0; i<n; ++i)
{
dis[i]=INF;
vis[i]=0;
}
dis[1]=0;
int v;
for(i=1; i<=n; ++i)
{
min=INF;
for(j=1; j<=n; ++j)
{
if(!vis[j]&&d[j]<min) //每次找点的过程,首先这个点没有被发现,然后找一个最小点
{
min=d[j];
v=j;
}
}
vis[v]=1;
for(j=1; j<=n; ++j) //加进最小点后,再修改从源点没有被发现的点的最短路径
{
if(!vis[j]&&dis[v]+mp[v][j]<dis[j])
dis[j]=dis[v]+mp[v][j];
}
}
int ans=-1;
for(i=1; i<=n; ++i)
if(dis[i]>ans)
ans=dis[i];
}
int main()
{
for(int i=0; i<n; ++i) //初始化 类似prim
for(int j=0; j<n; ++j)
{
if(i!=j)
mp[i][j]=INF;
else
mp[i][j]=0;
}
while(m--)
{
mp[i][j]=mp[j][i]=~~;
}
Dijstra();
return 0;
}
=============================================================
//Dijstra 优先队列+邻接表
//优化后复杂度为O(mlogn)
struct point
{
int val,id;
point(int id,int val):id(id),val(val) {}
bool operator <(const point &x)const
{
return val>x.val;
}
};
void dijkstra(int s)
{
memset(vis,0,sizeof(vis));
for(int i=0; i<n; i++)
dis[i]=INF;
priority_queue<point> q;
q.push(point(s,0));
vis[s]=true;
dis[s]=0;
while(!q.empty())
{
int cur=q.top().id;
q.pop();
vis[cur]=true;
for(int i=head[cur]; i!=-1; i=e[i].next)
{
int id=e[i].to;
if(!vis[id] && dis[cur]+e[i].val < dis[id])
{
dis[id]=dis[cur]+e[i].val;
q.push(point(id,dis[id]));
}
}
}
}
=============================================================
//Floyd O(n^3)
void Floyd()
{
for(int i=0; i<n; ++i)
for(int j=0; j<n; ++j)
mp[i][j]=mp[j][i]=~~;
for(int i=0; i<n; ++i)
for(int j=0; j<n; ++j)
for(int k=0; k<n; ++k)
if(mp[j][k]>mp[j][i]+mp[i][k])
mp[j][k]=mp[j][i]+mp[i][k];
}
=============================================================
//Spfa 邻接矩阵 O(kE)E为边数,k一般为2或3
//可以计算带负环的回路
void Spfa()
{
int i,j;
for(int i=0; i<n; ++i)
{
d[i]=INF;
vis[i]=0;
}
queue<int>q;
q.push(start);
d[start]=0;
vis[start]=1;
while(!q.empty())
{
int v=q.front();
q.pop();
vis[v]=0; // 这点别忘记
for(i=0; i<n; ++i)
{
if(d[i]>d[v]+mp[v][i])
{
d[i]=d[v]+mp[v][i];
if(!vis[i])
{
q.push(i);
vis[i]=1;
}
}
}
}
int ans=-1;
for(i=1; i<=n; ++i)
if(d[i]>ans)
ans=d[i];
}
int main()
{
for(int i=0; i<n; ++i)
for(int j=0; j<n; ++j)
{
if(i!=j)
mp[i][j]=INF;
else
mp[i][j]=0;
}
while(m--)
{
mp[i][j]=mp[j][i]=~~;
}
Spfa();
return 0;
}
=============================================================
//Spfa 邻接表(推荐)
struct node
{
int v;
int next;
int cost;
} Edge,e[M];
//Insert
//无向图的spfa的边要存两遍
void addEdge()
{
mp[cnt].v=to;
mp[cnt].cost=cost;
mp[cnt].next=headlist[from];
headlist[from]=cnt++;
mp[cnt].v=to;
mp[cnt].cost=cost;
mp[cnt].next=headlist[from];
headlist[from]=cnt++;
}
//
void Spfa()
{
int i,j;
for(i=0; i<n; ++i)
{
d[i]=INF;
vis[i]=0;
}
d[start]=0;
vis[start]=1;
queue<int>q;
q.push(start);
while(!q.empty())
{
int v==q.front();
q.pop();
vis[v]=0;
for(i=headlist[v]; i!=-1; i=mp[i].next)
{
int b=mp[i].v;
if(d[b]>d[v]+mp[i].cost)
{
d[b]=d[v]+mp[i].cost;
if(!vis[b])
{
vis[b]=1;
q.push(b);
}
}
}
}
int ans=-1;
for(i=1; i<n; ++i)
{
if(d[u]>ans)
ans=d[i];
}
}
int main()
{
//for(i=1; i<=n; i++)
// headlist[i]=-1;
memset(head,-1,sizeof(head));
cnt=0;
cin>>from>>to>>cost;
// Insert(edge1,head1,u,v,w);
// Insert(edge2,head2,v,u,w);
}
最短路问题(short-path problem),从某点出发到达另一点所经过的路径权值相加最小的一条路径,就是最短路径。
经典的也是最容易掌握的方法Floyd,Dijkstra两种算法。
1.Floyd算法
Floyd算法可以求解的是任意两点的最短路径,功能强大,因此复杂度也很高,但是非常的好懂。
思想很简单,两点的最短路径无非就是直接从一点到另一点,要么就是中间还存在着其他的点。因此只需要暴力枚举中间点,看看存不存在中间路径长度大于直接路径的长度即可。
给出模板(附带记录路径)
[cpp] view plain copy
#include <bits/stdc++.h>
#define inf 0x3f3f3f3f
using namespace std;
int G[105][105];
int n,m;//n边m点
int next[105][105];
void init()
{
cin>>n>>m;
for(int i=0; i<=n; i++)//初始化
{
for(int j=0; j<=n; j++)
{
if(i==j)
G[i][j]=G[j][i]=0;
else G[i][j]=G[j][i]=inf;
}
}
for(int i=0; i<m; i++)
{
int x,y,w;
cin>>x>>y>>w;
if(G[x][y]>w)//有些题目坑人给重复路径
G[x][y]=G[y][x]=w;
}
}
void printpath(){
int st=1,ed=n;
while(st!=ed){
cout<<st<<"->";
st=next[st][ed];
}
cout<<ed<<endl;
}
void Floyd()
{
for(int i=1; i<=n; i++)//初始化路径
for(int j=1; j<=n; j++)
next[i][j]=j;
for(int k=1; k<=n; k++)
for(int i=1; i<=n; i++)
for(int j=1; j<=n; j++)
if(G[i][j]>G[i][k]+G[k][j])
{
G[i][j]=G[i][k]+G[k][j];
next[i][j]=next[i][k];
}
cout<<G[1][n]<<endl;
printpath();
}
int main()
{
init();
Floyd();
return 0;
}
代码非常的简洁明了。是最容易弄懂的一个算法。但是复杂度偏高,针对有些题目没必要全部算出最短路,只需要算出单点的最短路。因此有时候复杂度会超限。
2.Dijkstra算法
说实话Dijkstra算法有点像最小生成树,也是开一个数组然后不断更新更新。
Dijkstra算法是先纳入起始点到点集合,然后dis[j]记录的是这个点集合到这个j这个点的距离。因此不断的去纳入新的点,去更新现有的距离。
给出一个带记录路径的模板
[cpp] view plain copy
#include <bits/stdc++.h>
#define inf 0x3f3f3f3f
using namespace std;
int G[105][105];
int n,m;//n边m点
bool vis[105];
int dis[105],pre[105];
void init()
{
cin>>n>>m;
for(int i=0; i<=n; i++)//初始化
{
for(int j=0; j<=n; j++)
{
if(i==j)
G[i][j]=G[j][i]=0;
else G[i][j]=G[j][i]=inf;
}
}
for(int i=0; i<m; i++)
{
int x,y,w;
cin>>x>>y>>w;
if(G[x][y]>w)//有些题目坑人给重复路径
G[x][y]=G[y][x]=w;
}
}
void printpath(int v){
int p=n,cnt=0;
int ans[105];
while(p!=v){
ans[cnt++]=p;
p=pre[p];
}
cout<<v;
for(int i=cnt-1;i>=0;i--)
cout<<"->"<<ans[i];
cout<<endl;
}
void Dijkstra(int v)
{
int minnum,next;
for(int i=0; i<=n; i++)
{
dis[i]=G[v][i];
vis[i]=0;
if(i!=v&&G[v][i]!=inf)
pre[i]=v;
else pre[i]=-1;
}
vis[v]=1;
dis[v]=0;
for(int i=1; i<n; i++) //每次纳入一个点,需要操作n-1次
{
minnum=inf;
for(int j=1; j<=n; j++)//找出距离最小的点当做下一个操作点
{
if(!vis[j]&&minnum>dis[j])
{
minnum=dis[j];
next=j;
}
}
if(minnum==inf)
break;
vis[next]=1;
for(int j=1; j<=n; j++) //更新这个操作点到其他点的距离
{
if(!vis[j] && G[next][j]!=inf && dis[next]+G[next][j]<dis[j])
{
dis[j]=G[next][j]+dis[next];
pre[j]=next;
}
}
}
cout<<dis[n]<<endl;
printpath(v);
}
int main()
{
init();
Dijkstra(1);//起始点到最后个点的距离
return 0;
}
3.Bellman-Ford算法
从百度上弄来的代码,很直观很好理解,对每条边,跑n-1次,每次松弛(妈的,什么叫松弛!?其实就是判断纳入这条边之后对最短路有没有影响,在具体点就是这行代码
[cpp] view plain copy
dis[edge[j].v] > dis[edge[j].u] + edge[j].cost
)我是不太喜欢那些被装饰的语句的,能简单就简单,能用代码讲清楚的就别搞七搞八。
另spfa是该算法的队列优化
[cpp] view plain copy
#include<bits/stdc++.h>
using namespace std;
#define MAX 0x3f3f3f3f
#define N 1010
int nodenum, edgenum, original; //点,边,起点
struct Edge //边
{
int u, v;
int cost;
};
Edge edge[N];
int dis[N], pre[N];//dis是起点到这个点的最短路,pre存路径
bool Bellman_Ford()
{
for(int i = 1; i <= nodenum; ++i) //初始化
dis[i] = (i == original ? 0 : MAX);
for(int i = 1; i <= nodenum - 1; ++i)//跑这么多次
for(int j = 1; j <= edgenum; ++j)//每次跑所有的边
if(dis[edge[j].v] > dis[edge[j].u] + edge[j].cost) //松弛(顺序一定不能反~)
{
dis[edge[j].v] = dis[edge[j].u] + edge[j].cost;
pre[edge[j].v] = edge[j].u;
}
bool flag = 1; //判断是否含有负权回路
for(int i = 1; i <= edgenum; ++i)//检验最短路中是否存在负权
if(dis[edge[i].v] > dis[edge[i].u] + edge[i].cost)
{
flag = 0;
break;
}
return flag;
}
void print_path(int root) //打印最短路的路径(反向)
{
while(root != pre[root]) //前驱
{
printf("%d-->", root);
root = pre[root];
}
if(root == pre[root])
printf("%d\n", root);
}
int main()
{
scanf("%d%d%d", &nodenum, &edgenum, &original);//点,边,起点
pre[original] = original;
for(int i = 1; i <= edgenum; ++i)
{
scanf("%d%d%d", &edge[i].u, &edge[i].v, &edge[i].cost);
}
if(Bellman_Ford())
for(int i = 1; i <= nodenum; ++i) //起点到每个点最短路
{
printf("%d\n", dis[i]);
printf("Path:");
print_path(i);
}
else
printf("have negative circle\n");
return 0;
}
根据pat考试中L2-001题目整理的完美模板,包含一切
[cpp] view plain copy
#include<bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
int n,m,st,ed;
int num[505],vis[505],dis[505],pre[505],sum[505],cnt[505];
//每个节点的人数。是否走过。最短距离。前驱。最大救援人数数量.方案数
int G[505][505];//图
void path(int i){
if(pre[i]!=-1){
path(pre[i]);
cout<<pre[i]<<" ";
}
}
int main()
{
cin>>n>>m>>st>>ed;
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
if(i==j)
G[i][j]=0;
else G[i][j]=inf;
}
}
for(int i=0;i<n;i++)
cin>>num[i];
for(int i=0;i<m;i++){
int x,y,w;
cin>>x>>y>>w;
G[x][y]=G[y][x]=w;
}
memset(dis,inf,sizeof(dis));
memset(vis,0,sizeof(vis));
memset(cnt,0,sizeof(cnt));
memset(sum,0,sizeof(sum));
memset(pre,-1,sizeof(pre));
dis[st]=0;
vis[st]=1;
cnt[st]=1;
sum[st]=num[st];
for(int i=0;i<n;i++){
int minnum=inf,next=st;
for(int j=0;j<n;j++){
if(vis[j]==0&&minnum>dis[j]){
minnum=dis[j];
next=j;
}
}
vis[next]=1;
for(int j=0;j<n;j++){
if(vis[j]==0){
if(dis[j]>dis[next]+G[next][j]){
dis[j]=dis[next]+G[next][j];
sum[j]=num[j]+sum[next];
cnt[j]=cnt[next];
pre[j]=next;
}
else if(dis[j]==dis[next]+G[next][j]){
cnt[j]=cnt[next]+cnt[j];
if(sum[j]<sum[next]+num[j]){
sum[j]=sum[next]+num[j];
pre[j]=next;
}
}
}
}
}
cout<<cnt[ed]<<" "<<sum[ed]<<endl;
path(ed);
cout<<ed<<endl;
return 0;
}
DIJK
C++代码 收藏代码
#define inf 0x3fffffff
#define M 105
int dist[M], map[M][M], n;
bool mark[M];
void init ()
{
int i, j;
for (i = 1; i <= n; i++) //i==j的时候也可以初始化为0,只是有时候不合适
for (j = 1; j <= n; j++)
map[i][j] = inf;
}
void dijk (int u)
{
int i, j, mins, v;
for (i = 1; i <= n; i++)
{
dist[i] = map[u][i];
mark[i] = false;
}
mark[u] = true;
dist[u] = 0; //既然上面的map当i==j时不是0,就要这句
while (1)
{
mins = inf;
for (j = 1; j <= n; j++)
if (!mark[j] && dist[j] < mins)
mins = dist[j], v = j;
if (mins == inf)
break;
mark[v] = true;
for (j = 1; j <= n; j++)
if (!mark[j] && dist[v] + map[v][j] < dist[j])
dist[j] = dist[v] + map[v][j];
}
}
②Floyd
C++代码 收藏代码
#define inf 0x3fffffff //注意,太大会溢出
#define M //最大点数
int n, dist[M][M]; //n:实际点数
void init () //有时候需要初始化
{
int i, j;
for (i = 1; i <= n; i++)
for (j = i + 1; j <= n; j++)
dist[i][j] = dist[j][i] = inf;
}
void floyd ()
{
int i, j, k;
for (k = 1; k <= n; k++)
for (i = 1; i <= n; i++)
for (j = 1; j <= n; j++) //有的题目会溢出就要自己变通了
if (dist[i][k] + dist[k][j] < dist[i][j])
dist[i][j] = dist[i][k] + dist[k][j];
}
③vector后插的SPFA
C++代码 收藏代码
#define inf 0x3fffffff
#define M 105 //最大点数
struct son{
int v, w;
};
vector<son> g[M];
bool inq[M]; //入队列标记
int dist[M], n; //n:实际点数
void init ()
{
for (int i = 1; i <= n; i++)
g[i].clear();
}
void spfa (int u)
{
int i, v, w;
for (i = 1; i <= n; i++)
{
dist[i] = inf;
inq[i] = false;
}
queue<int> q;
q.push (u);
inq[u] = true;
dist[u] = 0;
while (!q.empty())
{
u = q.front();
q.pop();
inq[u] = false;
for (i = 0; i < g[u].size(); i++)
{
v = g[u][i].v;
w = g[u][i].w;
if (dist[u] + w < dist[v])
{
dist[v] = dist[u] + w;
if (!inq[v])
{
q.push (v);
inq[v] = true;
}
}
}
}
}
④模拟前插的SPFA(多数情况下比③快,数据较为复杂就会看出来)
C++代码 收藏代码
#define inf 0x3fffffff
#define M 1005 //最大点数
struct edge{
int v, w, next;
}e[10005]; //估计好有多少条边
int pre[M], cnt, dist[M], n;
bool inq[M];
//注意初始化
void init ()
{
cnt = 0;
memset (pre, -1, sizeof(pre));
}
//注意双向加边
void addedge (int u, int v, int w) //加边函数,慢慢模拟就会明白的
{
e[cnt].v = v;
e[cnt].w = w;
e[cnt].next = pre[u]; //接替已有边
pre[u] = cnt++; //自己前插成为u派生的第一条边
}
void spfa (int u)
{
int v, w, i;
for (i = 1; i <= n; i++) //对于从1到n的编号
dist[i] = inf, inq[i] = false;
dist[u] = 0;
queue<int> q;
q.push (u);
inq[u] = true;
while (!q.empty())
{
u = q.front();
q.pop();
inq[u] = false;
for (i = pre[u]; i != -1; i = e[i].next)
{
w = e[i].w;
v = e[i].v;
if (dist[u] + w < dist[v])
{
dist[v] = dist[u] + w;
if (!inq[v])
{
q.push (v);
inq[v] = true;
}
}
}
}
}
#include <bits/stdc++.h>
using namespace std;
const int maxn = 99999;
const int INF = 0x7fffffff;
typedef pair<int, int> P;
struct edge
{
int to, cost;
};
int V;
vector<edge> G[maxn];
int d[maxn];
//
//bool operator <(const P a, const P b) {
// return a.first > b.first;
//}
void dijkstra(int s)
{
priority_queue<P, vector<P>, greater<P> > que;
fill(d, d + V + 2, INF);
d[s] = 0;
que.push(P(0, s));
while (!que.empty()) {
P p = que.top();
que.pop();
int v = p.second;
if (d[v] < p.first) continue;//到v点的距离如果已经被更新 这无须执行以下操作
for (int i = 0; i < G[v].size(); ++i) {
edge e= G[v][i];
if (d[e.to] > d[v] +e.cost) {
d[e.to] = d[v] + e.cost;
que.push(P(d[e.to], e.to));
}
}
}
}
int main()
{
int m;
cin >> V >> m;
for (int i = 0; i < m; ++i) {
int from, to, cost;
cin >> from >> to >> cost;
edge var;
var.to = to;
var.cost = cost;
G[from].push_back(var);
var.to = from;
G[to].push_back(var);
}
for (int i = 1; i <= V; ++i) {
cout << i << ":";
for(int j = 0; j < G[i].size(); ++j)
cout << G[i][j].to << " ";
cout << endl;
}
dijkstra(1);
for (int i = 1; i <= V; ++i)
cout << d[i] << endl;
return 0;
}
#include <bits/stdc++.h>
using namespace std;
const int INF = 0x7fffffff;
#define MEM(arr) memset(arr, 0, sizeof(arr))
const int maxn = 5000;
typedef struct node
{
int to, cost;
}edge;
vector<edge> v[maxn];
int in_sum[maxn];
int in_que[maxn];
int n, m, d[maxn];
bool SPFA(int source)
{
deque<int> q;
for (int i = 1; i <= n; ++i) {
d[i] = i == source ? 0 : INF;
}
q.push_back(source);
in_sum[source]++;
in_que[source] = 1;
while (!q.empty()) {
int curr = q.front();
q.pop_front();
in_que[curr] = 0;
for (int i = 0; i < v[curr].size(); ++i) {
int to = v[curr][i].to;
if (d[curr] < INF && d[to] > d[curr] + v[curr][i].cost) {
d[to] = d[curr] + v[curr][i].cost;
if (in_que[to] == 0) {
in_que[to] = 1;
if(++in_sum[to] == n)
return false;
}
if(!q.empty())
{
if(d[to] > d[q.front()]) q.push_back(to);
else q.push_front(to);
}else q.push_back(to);
}
}
}
return true;
}
int main()
{
while (cin >> n >> m) {
MEM(in_sum);
MEM(in_que);
MEM(d);
for (int i = 0; i <= n; ++i) {
v[i].clear();
}
for (int i = 1; i <= m; ++i) {
int vfrom, vto, vcost;
cin >> vfrom >> vto >> vcost;
edge var;
var.to = vto;
var.cost = vcost;
v[vfrom].push_back(var);
}
// for (int i = 1; i <= n; ++i) {
// cout << i << ":";
// for(int j = 0; j < v[i].size(); ++j)
// cout << v[i][j].to << " ";
// cout << endl;
// }
if(SPFA(1))
{
for (int i = 1; i <=n; ++i) {
cout << i << ":" << d[i] << endl;
}
}
else cout << "-1" << endl;
}
return 0;
}
/*
Dijkstra的算法思想:
在所有没有访问过的结点中选出dis(s,x)值最小的x
对从x出发的所有边(x,y),更新
dis(s,y)=min(dis(s,y),dis(s,x)+dis(x,y))
*/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <vector>
using namespace std;
const int Ni = 10000;
const int INF = 0x3f3f3f3f;
struct node{
int x,d;
node(){}
node(int a,int b){x=a;d=b;}
bool operator < (const node & a) const
{
if(d==a.d) return x<a.x;
else return d > a.d;
}
};
vector<node> eg[Ni];
int dis[Ni],n;
void Dijkstra(int s)
{
int i;
memset(dis,INF,sizeof(dis));
dis[s]=0;
//用优先队列优化
priority_queue<node> q;
q.push(node(s,dis[s]));
while(!q.empty())
{
node x=q.top();
q.pop();
for(i=0;i<eg[x.x].size();i++)
{
node y=eg[x.x][i];
if(dis[y.x]>x.d+y.d)
{
dis[y.x]=x.d+y.d;
q.push(node(y.x,dis[y.x]));
}
}
}
}
int main()
{
int a,b,d,m;
while(scanf("%d%d",&n,&m),n+m)
{
for(int i=0;i<=n;i++)
eg[i].clear();
while(m--)
{
scanf("%d%d%d",&a,&b,&d);
eg[a].push_back(node(b,d));
eg[b].push_back(node(a,d));
}
Dijkstra(1);
printf("%d\n",dis[n]);
}
return 0;
}
/*
6
2 2
2 4
4 5
5 2
6 3
6 3
*/