Floyed-Warshall算法O(N^3)
弗洛伊德算法是最简单的最短路径算法,可以计算任意两点之间的最短路径,适用于出现负边权的情况。
for(k=1;k<=n;k++)
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
if(dis[i][j]>dis[i][k]+dis[k][j])
dis[i][j]=dis[i][k]+dis[k][j];
//或者dis[i][j]=dis[i][j]||(dis[i][k]&&dis[k][j]);
1342:【例4-1】最短路径问题
【题目描述】
平面上有n个点(n≤100),每个点的坐标均在-10000~10000之间。其中的一些点之间有连线。
若有连线,则表示可从一个点到达另一个点,即两点间有通路,通路的距离为两点间的直线距离。现在的任务是找出从一点到另一点之间的最短路径。
【输入】
共n+m+3行,其中:
第一行为整数n。
第2行到第n+1行(共n行) ,每行两个整数x和y,描述了一个点的坐标。
第n+2行为一个整数m,表示图中连线的个数。
此后的m 行,每行描述一条连线,由两个整数i和j组成,表示第i个点和第j个点之间有连线。
最后一行:两个整数s和t,分别表示源点和目标点。
【输出】
一行,一个实数(保留两位小数),表示从s到t的最短路径长度。
【输入样例】
5
0 0
2 0
2 2
0 2
3 1
5
1 2
1 3
1 4
2 5
3 5
1 5
【输出样例】
3.41
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
using namespace std;
int n,m,s,t,x,y;
int a[101][3];
double f[101][101];
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i][1]>>a[i][2];
cin>>m;
memset(f,0x7f,sizeof(f)); //先初始化为一个较大内容的数组
for(int i=1;i<=m;i++)
{
cin>>x>>y;
f[x][y]=f[y][x]=sqrt(pow(double(a[x][1]-a[y][1]),2)+pow(double(a[x][2]-a[y][2]),2));
}
cin>>s>>t;
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(i!=j&&i!=k&&j!=k&&f[i][j]>f[i][k]+f[k][j])
f[i][j]=f[i][k]+f[k][j];
printf("%.2lf",f[s][t]);
return 0;
}
1343:【例4-2】牛的旅行
【题目描述】
农民John的农场里有很多牧区。有的路径连接一些特定的牧区。一片所有连通的牧区称为一个牧场。但是就目前而言,你能看到至少有两个牧区不连通。现在,John想在农场里添加一条路径 ( 注意,恰好一条 )。对这条路径有这样的限制:一个牧场的直径就是牧场中最远的两个牧区的距离 ( 本题中所提到的所有距离指的都是最短的距离 )。考虑如下的两个牧场,图1是有5个牧区的牧场,牧区用“*”表示,路径用直线表示。每一个牧区都有自己的坐标:
图1所示的牧场的直径大约是12.07106, 最远的两个牧区是A和E,它们之间的最短路径是A-B-E。
这两个牧场都在John的农场上。John将会在两个牧场中各选一个牧区,然后用一条路径连起来,使得连通后这个新的更大的牧场有最小的直径。注意,如果两条路径中途相交,我们不认为它们是连通的。只有两条路径在同一个牧区相交,我们才认为它们是连通的。
现在请你编程找出一条连接两个不同牧场的路径,使得连上这条路径后,这个更大的新牧场有最小的直径。
【输入】
第 1 行:一个整数N (1 ≤ N ≤ 150), 表示牧区数;
第 2 到 N+1 行:每行两个整数X,Y ( 0 ≤ X,Y≤ 100000 ), 表示N个牧区的坐标。每个牧区的坐标都是不一样的。
第 N+2 行到第 2*N+1 行:每行包括N个数字 ( 0或1 ) 表示一个对称邻接矩阵。
例如,题目描述中的两个牧场的矩阵描述如下:
A B C D E F G H
A 0 1 0 0 0 0 0 0
B 1 0 1 1 1 0 0 0
C 0 1 0 0 1 0 0 0
D 0 1 0 0 1 0 0 0
E 0 1 1 1 0 0 0 0
F 0 0 0 0 0 0 1 0
G 0 0 0 0 0 1 0 1
H 0 0 0 0 0 0 1 0
输入数据中至少包括两个不连通的牧区。
【输出】
只有一行,包括一个实数,表示所求答案。数字保留六位小数。
【输入样例】
8
10 10
15 10
20 10
15 15
20 15
30 15
25 10
30 10
01000000
10111000
01001000
01001000
01110000
00000010
00000101
00000010【输出样例】
22.071068
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
using namespace std;
int n;
char ch;
int a[151][3];
double f[151][151],maxt=1e12,minx=1e20,temp,m[151];
double dist(int x,int y)
{
return sqrt(pow(double(a[x][1]-a[y][1]),2)+pow(double(a[x][2]-a[y][2]),2));
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i][1]>>a[i][2];
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
cin>>ch;
if(ch=='1')
f[i][j]=dist(i,j);
else
f[i][j]=maxt;
}
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(i!=k&&i!=j&&j!=k)
if(f[i][k]<maxt-1&&f[k][j]<maxt-1) //确保连通
if(f[i][j]>f[i][k]+f[k][j])
f[i][j]=f[i][k]+f[k][j];
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(f[i][j]<maxt-1&&m[i]<f[i][j]) m[i]=f[i][j]; //(1)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(i!=j&&f[i][j]>maxt-1)
{
temp=dist(i,j);
if(minx>m[i]+m[j]+temp)
minx=m[i]+m[j]+temp;
} //(4)
for(int i=1;i<=n;i++)
if(m[i]>minx) //(2)(5)
minx=m[i];
printf("%.6lf",minx);
return 0;
}
Dijkstra算法O(N^2)
用于计算一个点到其他所有点的最短路径的算法,是一种单源最短路径算法,也就是说,只能计算起点只有一个的情况
#include<iostream>
#include<fstream>
#include<string>
using namespace std;
#define MaxSize 10
#define MAXCOST 10000
// 图的结构
template<class T>
struct Graph
{
T vertex[MaxSize];// 存放图中顶点的数组
int arc[MaxSize][MaxSize];// 存放图中边的数组
int vertexNum, arcNum;// 图中顶点数和边数
};
// 最短路径Dijkstra算法
void Dijkstra(Graph<string> G,int v)
{
int dist[MaxSize];// i到j的路径长度
string path[MaxSize];// 路径的串
int s[MaxSize];// 已找到最短路径的点的集合
bool Final[MaxSize];//Final[w]=1表示求得顶点V0至Vw的最短路径
// 初始化dist\path
for (int i = 0; i < G.vertexNum; i++)
{
Final[i] = false;
dist[i] = G.arc[v][i];
if (dist[i] != MAXCOST)
path[i] = G.vertex[v] + G.vertex[i];
else
path[i] = " ";
}
s[0] = v; // 初始化s
Final[v] = true;
int num = 1;
while (num < G.vertexNum)
{
// 在dist中查找最小值元素
int k = 0,min= MAXCOST;
for (int i = 0; i < G.vertexNum; i++)
{
if (i == v)continue;
if (!Final[i] && dist[i] < min)
{
k = i;
min = dist[i];
}
}
cout << dist[k]<<path[k]<<endl;
s[num++] = k;// 将新生成的结点加入集合s
Final[k] = true;
// 修改dist和path数组
for (int i = 0; i < G.vertexNum; i++)
{
if (!Final[i]&&dist[i] > dist[k] + G.arc[k][i])
{
dist[i] = dist[k] + G.arc[k][i];
path[i] = path[k] + G.vertex[i];
}
}
}
}
int main()
{
// 新建图
Graph<string> G;
string temp[]= { "v0","v1","v2","v3","v4" };
/*int length = sizeof(temp) / sizeof(temp[0]);
G.vertexNum = length;
G.arcNum = 7;*/
ifstream in("input.txt");
in >> G.vertexNum >> G.arcNum;
// 初始化图的顶点信息
for (int i = 0; i < G.vertexNum; i++)
{
G.vertex[i] = temp[i];
}
//初始化图G的边权值
for (int i =0; i <G.vertexNum; i++)
{
for (int j = 0; j <G.vertexNum; j++)
{
G.arc[i][j] = MAXCOST;
}
}
for (int i = 0; i < G.arcNum; i++)
{
int m, n,cost;
in >> m >> n >> cost;
G.arc[m][n] = cost;
}
Dijkstra(G, 0);
system("pause");
return 0;
}
1344:【例4-4】最小花费
【题目描述】
在n个人中,某些人的银行账号之间可以互相转账。这些人之间转账的手续费各不相同。给定这些人之间转账时需要从转账金额里扣除百分之几的手续费,请问A最少需要多少钱使得转账后B收到100元。
【输入】
第一行输入两个正整数n,m,分别表示总人数和可以互相转账的人的对数。
以下m行每行输入三个正整数x,y,z,表示标号为x的人和标号为y的人之间互相转账需要扣除z%的手续费 (z<100)。
最后一行输入两个正整数A,B。数据保证A与B之间可以直接或间接地转账。
【输出】
输出A使得B到账100元最少需要的总费用。精确到小数点后8位。
【输入样例】
3 3
1 2 1
2 3 2
1 3 3
1 3
【输出样例】
103.07153164
【提示】
【数据规模】
1≤n≤2000
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
using namespace std;
double a[2001][2001],dis[2001],minn;
int f[2001],n,m,k,x,y;
void read()
{
int xx,yy,zz;
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
scanf("%d%d",&xx,&yy);
scanf("%lf",&a[xx][yy]);
a[xx][yy]=(100-a[xx][yy])/100;
a[yy][xx]=a[xx][yy];
}
cin>>x>>y;
}
void dijkstra(int x)
{
for(int i=1;i<=n;i++)
dis[i]=a[x][i];
dis[x]=1;
f[x]=1;
for(int i=1;i<=n-1;i++)
{
minn=0;
for(int j=1;j<=n;j++)
if(f[j]==0&&dis[j]>minn)
{
k=j;
minn=dis[j];
}
f[k]=1;
if(k==y) break;
for(int j=1;j<=n;j++)
if(f[j]==0&&dis[k]*a[k][j]>dis[j])
dis[j]=dis[k]*a[k][j];
}
}
int main()
{
read();
dijkstra(x);
printf("%.8lf",100/dis[y]);
return 0;
}
Bellman-Ford算法O(NE) //N是顶点数,E是边数
同样是用来计算从一个点到其他所有点的最短路径的算法,能够处理存在负边权问题的情况,单无法处理存在负权回路的情况。
初始化:
dis[s]=0,dis[v]=0x3f3f3f3f(v!=s),pre[s]=0;
for(i=1;i<=n-1;i++)
for(j=1;j<=E;j++)
if(dis[u]+w[i]<dis[v]) //u,v分别是这条边连接的两个点
{
dis[v]=dis[u]+w[j];
pre[v]=u;
}
SPFA算法O(kE)(k是常数,E是边数)
SPFA算法是Bellman-Ford算法的一种队列实现,减少了不必要的冗余计算。
dis[i]记录从起点s到i的最短路径,w[i][j]记录连接i,j的边的长度,pre[v]记录前趋,team[1]=s,head=0,tail=1,exist[s]=true;
do
{
1.头指针向下移一位,取出指向的点u
2.布尔数组exist[n]=false,已被取出队列
3.for与u相连的所有点v
if(dis[v]>dis[u]+w[u][v];
{
dis[v]=dis[u]+w[u][v];
pre[v]=u;
if(!exist[v]) //队列中不存在v点,v入列
{
尾指针下移一位,v入列
exist[v]=true;
}
}
}
while(head<tail)
1345:【例4-6】香甜的黄油
【题目描述】
农夫John发现做出全威斯康辛州最甜的黄油的方法:糖。把糖放在一片牧场上,他知道N(1≤N≤500)只奶牛会过来舔它,这样就能做出能卖好价钱的超甜黄油。当然,他将付出额外的费用在奶牛上。
农夫John很狡猾。像以前的巴甫洛夫,他知道他可以训练这些奶牛,让它们在听到铃声时去一个特定的牧场。他打算将糖放在那里然后下午发出铃声,以至他可以在晚上挤奶。
农夫John知道每只奶牛都在各自喜欢的牧场(一个牧场不一定只有一头牛)。给出各头牛在的牧场和牧场间的路线,找出使所有牛到达的路程和最短的牧场(他将把糖放在那)。
【输入】
第一行: 三个数:奶牛数N,牧场数P(2≤P≤800),牧场间道路数C(1≤C≤1450)。
第二行到第N+1行: 1到N头奶牛所在的牧场号。
第N+2行到第N+C+1行:每行有三个数:相连的牧场A、B,两牧场间距(1≤D≤255),当然,连接是双向的。
【输出】
一行 输出奶牛必须行走的最小的距离和。
【输入样例】
3 4 5
2
3
4
1 2 1
1 3 5
2 3 7
2 4 3
3 4 5
【输出样例】
8
【提示】
说明:放在4号牧场最优。
#include<bits/stdc++.h>
using namespace std;
const int N=10005;
const int inf=0x3f3f3f3f;
int n,p,c;
int head[N];
int s[N];
int cnt;
int vis[N];
int dis[N];
struct node
{
int next;
int from;
int to;
int dis;
}edge[N*2];
void add_edge(int from,int to,int dis)
{
edge[++cnt].next=head[from];
edge[cnt].to=to;
edge[cnt].dis=dis;
head[from]=cnt;
}
void spfa(int x)
{
memset(vis,0,sizeof(vis));
memset(dis,inf,sizeof(dis));
queue<int>Q;
Q.push(x);
vis[x]=1;
dis[x]=0;
while(!Q.empty()){
int u=Q.front();
Q.pop();
vis[u]=0;
for(int i=head[u];i!=0;i=edge[i].next){
int to=edge[i].to;
int di=edge[i].dis;
if(dis[to]>dis[u]+di){
dis[to]=dis[u]+di;
if(!vis[to]){
vis[to]=1;
Q.push(to);
}
}
}
}
}
int main()
{
ios::sync_with_stdio(false);
cin>>n>>p>>c;
for(int i=1;i<=n;i++){
cin>>s[i];
}
memset(head,0,sizeof(head));
int a,b,cc;
for(int i=1;i<=c;i++){
cin>>a>>b>>cc;
add_edge(a,b,cc);
add_edge(b,a,cc);
}
int minn=inf;
int sum=0;
for(int i=1;i<=p;i++){
spfa(i);
sum=0;
for(int j=1;j<=n;j++){
sum+=dis[s[j]];
}
if(minn>sum) minn=sum;
}
cout<<minn<<endl;
return 0;
}