刷题笔记:最短路径(Dijkstra算法)

算法

最短路径问题是:给定图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两点间的最短路径问题。具体思想可以参考

胡凡的《算法笔记》

练习题(牛客网-考研复试机试题

  1. 最短路径问题
    最短路径距离+边权,可以直接套用代码。

  2. 最短路径
    这里有点意思,可以直接使用最短路径的做法,同时也可以使用并查集。应为第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;
}
  1. 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;
}

内容参考自胡凡的《算法笔记》

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值