算法
最短路径问题是:给定图G(V,E),求一条从起点到终点的路径,使得这条路径上经过的所有边的边权之和最小。
对任意给出的图G(V,E)和起点S、终点T,如何求从S到T的最短路径。解决最短路径问题的常用算法有Dijkstra算法、Bellman-Ford算法,SPFA算法和Floyd算法。这里主要介绍Dijkstra算法。
Dijkstra算法
Dijkstra算法用来解决单源最短路径问题,即给定图G(V,E)和起点S,通过算法得到S到达其他每个顶点的最短距离,但是不能处理有负权边的情况。
Dijkstra算法的基本思想是对图G设置集合S,存放已被访问的顶点,然后每次从集合V-S中选择与起点S的最短距离最小的一个顶点(记为u),访问并加入结合S,之后,令顶点u为中介点,优化起点S与所有从u能到达的顶点v之间的最短距离。这样的操作执行n次(n为顶点数),知道集合S已包含所有顶点。
以邻接举证为例,求出起点s到每个节点的最短路径距离,同时求出节点s到节点n的最短路径。
int map[MAXV][MAXV]={INF};
int dis[MAXV]; //dis[i]起点s到节点i的最短距离
bool vid[MAXV]={false}; //vis[i]表示节点i是否已经被访问过饿了
int pre[MAXV]={-1}; //pre[i]表示节点i的前一个节点,记录最短路径
void Dijkstra(int s,int n)
{
fill(dis,dis+MAXV,INF);
dis[s]=0;
for(int i=0;i<n;i++){
int u=-1,MIN=INF;
//寻找u使dis[u]最小,MIN存放最小的dis[u]
for(int j=0;j<n;j++){
if(vis[j]==false&&dis[j]<MIN){
u=j;
MIN=dis[j];
}
}
//找不到小于INF的dis[u]说明剩下的顶点和起点s不连通
if(u==-1) return;
vis[u]=true;//标记u为已访问
for(int v=0;v<n;v++){//使用u来更新其他顶点
if(vis[v]==false&&map[u][v]!=INF&&dis[u]+map[u][v]<dis[v]){
dis[v]=dis[u]+map[u][v];
pre[v]=u;//记录最短路径
}
}
}
}
void DFS(int s,int v)//递归输出完整的最短路径
{
if(s==v) { cout<<s<<endl; return;}
else{ dfs(s,pre[v]); cout<<v<<endl;} //递归访问v的前驱节点
}
以上为基本用法,当存在两条及以上可以达到的最短距离的路径,题目会给出第二标尺(第一标尺为距离),要求在所有最短路径中选择第二标尺最优的一条路径,常见标尺如下:
1、增加边权(如花费)
for(int v=0;v<n;v++){
if(vis[v]==false&&g[u][v]!=INF&&g[u][v]+dis[u]<dis[v]){
dis[v]= g[u][v]+dis[u];
c[v]=c[u]+cost[v];
}else if(vis[v]==false&&g[u][v]!=INF&&g[u][v]+dis[u]==dis[v]&&w[u]+cost[v]>c[v]){
c[v]=c[u]+cost[v];//最短距离相同时看能否使c[v]更优
}
}
2、增加点权(每个城市的人数)
for(int v=0;v<n;v++){
if(vis[v]==false&&g[u][v]!=INF&&g[u][v]+dis[u]<dis[v]){
dis[v]= g[u][v]+dis[u];
w[v]=w[u]+weight[v];
}else if(vis[v]==false&&g[u][v]!=INF&&g[u][v]+dis[u]==dis[v]&&w[u]+weight[v]>w[v]){
w[v]=w[u]+weight[v];//最短距离相同时看能否使w[v]更优
}
}
3、求解最短路径条数
for(int v=0;v<n;v++){
if(vis[v]==false&&g[u][v]!=INF&&g[u][v]+dis[u]<dis[v]){
dis[v]= g[u][v]+dis[u];
num[v]=num[u];
}else if(vis[v]==false&&g[u][v]!=INF&&g[u][v]+dis[u]==dis[v]{
num[v]+=num[u];
}
}
此外,Bellman-Ford算法处理带有负权边的单源最短路径问题;Floyd算法用来解决全源最短路径问题,即给出图G(V,E),求任意u,v两点间的最短路径问题。具体思想可以参考
胡凡的《算法笔记》
练习题(牛客网-考研复试机试题)
-
最短路径问题
最短路径距离+边权,可以直接套用代码。 -
最短路径
这里有点意思,可以直接使用最短路径的做法,同时也可以使用并查集。应为第k条路的长度2^k,一定比之前k-1条路的长度之和更长。当输入的两个节点a,b,他们已经属于一个集合,不需要更新距离;如果不在一个集合,则使用输入的边更新a,b两个节点间的距离。代码如下。
#include<iostream>
#include<vector>
using namespace std;
vector<int> parent;
vector<vector<int> >edge;
void init(int n)
{
parent.resize(n);
edge.resize(n);
for(int i=0;i<n;i++){
parent[i]=i;
edge[i].resize(n);
for(int j=0;j<n;j++){
edge[i][j]=-1;
}
edge[i][i]=0;
}
}
int find(int x)
{
if(x==parent[x]) return x;
else{
int f=find(parent[x]);
parent[x]=f;
return f;
}
}
int get_dist(int k)
{
int dis=1;
while(k>0){
dis=(dis*2)%100000;
k--;
}
return dis;
}
int main()
{
int n,m;
cin>>n>>m;
init(n);
int a,b,dis;
for(int i=0;i<m;i++){
cin>>a>>b;
dis=get_dist(i);
int fa=find(a);
int fb=find(b);
if(fa==fb) continue; //在同一个集合中,不需要在更新距离
for(int j=0;j<n;j++){
if(find(j)!=fa) continue; //i与a不在同一个集合中,无法到达
for(int k=0;k<n;k++){
if(find(k)!=fb) continue;
edge[k][j]=edge[j][k]=(edge[j][a]+dis+edge[b][k])%100000;
}
}
parent[fa]=fb;
}
for(int i=1;i<n;i++)
cout<<edge[0][i]<<"\n";
return 0;
}
-
I Wanna Go Home
这道题中,将节点分为1,2两类,只允许有一条从1类节点到2类节点的道路,我们还是可以使用Dijkstra算法,在更新节点的过程需要改变方式。代码如下。
#include<iostream>
#include<vector>
#include<algorithm>
#include<string>
#define INF 0x3fffffff
#define N 605
using namespace std;
int map[N][N],kind[N];
int dis[N];
bool vis[N]={false};
void init(int n)
{
for(int i=0;i<n;i++){
dis[i]=INF;
kind[i]=0;
vis[i]=false;
for(int j=0;j<n;j++){
map[i][j]=INF;
}
map[i][i]=0;
}
}
void Dijkstra(int s,int n)
{
dis[s]=0;
for(int i=0;i<n;i++){ //寻找最小的u和dis[u]
int u=-1,MIN=INF;
for(int j=0;j<n;j++){
if(vis[j]==false&&dis[j]<MIN){
MIN=dis[j];
u=j;
}
}
if(u==-1) return;
vis[u]=true;
for(int v=0;v<n;v++){
if(kind[u]==1){ //1类城市更新过程中可以使用任意类的城市更新
if(vis[v]==false&&map[u][v]!=INF&&map[u][v]+dis[u]<dis[v]){
dis[v]=map[u][v]+dis[u];
}
}else{ //如果节点u为2类,则说明已经有一天1->2的道路,之后只可以选择2类城市
if(kind[v]==2&&vis[v]==false&&map[u][v]!=INF&&map[u][v]+dis[u]<dis[v]){
dis[v]=map[u][v]+dis[u];
}
}
}
}
}
int main()
{
int n,m;
while(cin>>n&&n){
init(n);
cin>>m;
int a,b,d,c;
for(int i=0;i<m;i++){
cin>>a>>b>>d;
if(d<map[a-1][b-1]) map[a-1][b-1]=map[b-1][a-1]=d;
}
for(int i=0;i<n;i++){
cin>>c;
kind[i]=c;
}
Dijkstra(0, n);
if(dis[1]==INF) cout<<"-1\n";
else cout<<dis[1]<<"\n";
}
return 0;
}
内容参考自胡凡的《算法笔记》