PAT 备考——图论算法(二)最短路径

版权声明:Dirichlet_zju https://blog.csdn.net/Dirichlet_zju/article/details/84576426

最短路径算法是PAT甲级考试常考算法,具体说来,最短路径包括Dijkstra算法、Floyd算法,其余的Bellman-Ford和SPFA基本不会考(参《算法笔记》胡凡,曾磊著)

目录

一、最短路径基本概念与问题分类

1.基本概念

2.问题分类

二、Dijkstra算法

1.伪代码

2.示意图

3.参考代码

4.升级

5.Dijkstra+DFS


 


一、最短路径基本概念与问题分类

1.基本概念

给定图G(V,E),求一条从起点到终点的路径,是的这条路径上经过的所有边的边权之和最小。

2.问题分类

无负权单源最短路径:Dijkstra算法

有负权单源最短路径:Bellman-Ford算法(简称BF算法)以及BF算法的优化算法:SPFA(Shortest Path Faster Algorithm)

全源最短路径问题:Floyd算法

二、Dijkstra算法

1.伪代码

//G为图,一般设为全局变量;数组d为原点到达各个点的最短路径的长度,s为起点
Dijkstra(G, d[], s){
    初始化;
    for(循环n次){
        u=未访问过且d[u]最小的顶点标号;
        记u被访问过;
        for(从u出发能到达的所有顶点v){
            if(v未被访问 && 以u为中介点使s到顶点v的距离d[v]更小){
                优化d[v];
            }
        }
    }
    
}

2.示意图

3.参考代码

由于图可以用邻接矩阵和邻接表来实现,因此最短路径Dijkstra算法也有两种写法 。

3.1.邻接矩阵版

const int maxv=1000;
const int inf=1000000000;//无穷大量,也可以写作0x3fffffff(7个)

int n,G[maxv][maxv];//n为顶点数,G是邻接矩阵
int d[maxv];    //用于记录最短路径
bool vis[maxv]={false};    //用于记录是否已访问

void Dijkstra(int s){//s为源点
    fill(d, d+maxv, inf);
    d[s]=0;
    for(int i=0; i<n; i++){
        int u=-1, MIN=INF;
        for(int j=0; j<n; j++){
            if(vis[j]==false && d[j]<MIN){
                u=j;
                MIN=d[j];
            }
        }
        //找不到小于INF的d[u],说明剩下的顶点和起点不连通
        if(u==-1) return;
        vis[u]=true;
        //如果v未访问 && v能到达 && u能优化d[v]
        for(int v=0; v<n; v++){
            if(vis[v]==false && G[u][v]!=0 && d[v]>d[u]+G[u][v]){
                d[v]=d[u]+G[u][v];//优化
            }
        }
    }
}

3.2.邻接表版

struct Node{
    int w;
    int data;
};
vector<Node> Adj[maxn];
int n, dis[maxn];
bool vis[maxn] = {false};
void Dijkstra(int s){
    fill(dis, dis+maxn, inf);
    dis[s]=0;
    for(int i=0; i<n; i++){
        int u=-1; MIN=inf;
        for(int j=0; j<n; j++){
            if(vis[j]==false && dis[j]<MIN){
                u=j;
                MIN=dis[j];
            }
        }
        if(u==-1) return;
        vis[u]=true;
        //只有下面部分不一样
        for(int j=0; j<Adj[u].size(); j++){
            v=Adj[u][j].data;//直接获得该连接点v
            if(vis[v]==false && d[v]>d[u]+Adj[u][v].w){
                d[v]=d[u]+Adj[u][v].w;
            }
        }
    }
}

 3.3.路径求法

上面讲的方法是计算每个点到源点最小距离,没有得出源点到该点的具体路径。

下面写最短路径的求法:

一般来说,我们需要在上面的基础上增加一个数组pre[maxn],其中的每个数据pre[v]表示使v点为当前最短路径长度这样的情况下的前驱点(前面一个点,也就是例子中的u)。也就是说,在更新d[v]的时候顺便将pre[v]=u。

以邻接矩阵为例,见修改后的代码(注意看“新添加”字样):

const int maxv=1000;
const int inf=1000000000;//无穷大量,也可以写作0x3fffffff(7个)

int n,G[maxv][maxv];//n为顶点数,G是邻接矩阵
int d[maxv];    //用于记录最短路径
int pre[maxv];    //用于记录每个点的前驱点————————————————————————————(新添加)
bool vis[maxv]={false};    //用于记录是否已访问

void Dijkstra(int s){//s为源点
    fill(d, d+maxv, inf);
    for(int i=0; i<n; i++) pre[i]=i;    //初始化—————————————————————(新添加)
    d[s]=0;
    for(int i=0; i<n; i++){
        int u=-1, MIN=INF;
        for(int j=0; j<n; j++){
            if(vis[j]==false && d[j]<MIN){
                u=j;
                MIN=d[j];
            }
        }
        //找不到小于INF的d[u],说明剩下的顶点和起点不连通
        if(u==-1) return;
        vis[u]=true;
        //如果v未访问 && v能到达 && u能优化d[v]
        for(int v=0; v<n; v++){
            if(vis[v]==false && G[u][v]!=0 && d[v]>d[u]+G[u][v]){
                d[v]=d[u]+G[u][v];//优化
                pre[v]=u;    //记录前驱点    —————————————————————————(新添加)
            }
        }
    }
}

完成了这一步,在我们的数组pre中就记录了每个点的上一个点,上一个点也可以找到他的前一个点,直到找到源点,再将其从原点开始输出。这样一个过程其实就是递归 :

void PrintPath(int s, int v){//从源点s到结点v的最短路径
    if(v==s){//当递归到源点时
        cout<<s<<endl;
        return;
    }
    PrintPath(s,pre[v]);//递归
    cout<<v<<endl;//最后一个点递归结束开始输出
}

4.升级

至此,基本介绍了Dijkstra算法的基本使用方法,但是题目肯定不会考的这么直接,至少会给它加些包装或者升级。我们按照以往考题,总结出以下三种升级形式:

  • 给每条边增加一个边权(比如花费),然后要求在最短路径有多条时花费最小;
  • 或是给每个节点增加一个点权(比如每个城市能够收到的物资),然后在路径最短时收集到的物资最多;
  • 再或者直接问有多少条最短路径

下面针对这三种情况分别对代码进行修改:

//增加边权考法
/*增加int cost[][]数组,cost[u][v]表示u->v的花费
(类似于G[][]中的权重),并增加一个数组c[],c[u]表
 示从源点到u点的最小花费(类似于最短路径的数组d[])
 初始化:c[s]为0,其余全为inf*/
//则,在更新最短路径片段这样表示(邻接矩阵)
for(int v=0; v<n; v++){
    if(G[u][v]!=0 && vis[v]==false){
        //最短路径不同时更新路径与花费
        if(d[v]>d[u]+G[u][v]){
            d[v]=d[u]+G[u][v];
            c[v]=v[u]+cost[u][v];
        }
        //最短路径相同看花费较小
        else if(d[v]==d[u]+G[u][v] && c[v]>v[u]+cost[u][v]){
            c[v]=c[u]+cost[u][v];
        }
    }    
    
}
//增加点权考法,类似于增加边权
/*weight[u]表示城市中的物资数目,w[u]表示收集到的物资最大值。
  初始化时:只有w[s]为weight[s],其余初始化为0
  之后再更新最短路径更新即可*/
for(int v=0; v<n; v++){
    if(vis[v]==false && G[u][v]!=0){
        if(d[v]>d[u]+G[u][v]){
            d[v]=d[u]+G[u][v];
            w[v]=w[u]+weight[v];
        }
        else if(d[v]=d[u]+G[u][v] && w[v]<w[u]+weight[v]){
            w[v]=w[u]+weight[v];
        }
    }
}
//直接问最短路径有几条
/*增加数组num[]表示起点到该点的最短路径图条数
  初始化:只有num[s]为1,其余全为0*/
for(int v=0; v<n; v++){
    if(vis[v]==false && G[u][v]!=0){
        if(d[v]>d[u]+G[u][v]){
            d[v]=d[u]+G[u][v];
            num[v]=num[u];
        }
        else if(d[v]==d[u]+G[u][v]){
            num[v]=num[u]+num[v];//距离相同则累加
        }
    }
}

5.Dijkstra+DFS

 Dijkstra算法在进行多标尺或复杂情况处理还不尽完美,使用Dijkstra算法(计算最短路径)+DFS算法(按照标尺对各个路径进行比较)结合的方式可以让逻辑结构更为清晰。

要达到目的,首先需要调整pre[]数组改为vector<int> pre[maxn],这样在一个点可以存放所有具有最短路径的前置节点。

修改后的代码如下:

vector<int> pre[maxn];
void Dijkstra(int s){//开始节点
    //初始化
    fill(d, d+maxn, inf);//最短路径
    d[s]=0;
    //循环n次
    for(int i=0; i<n; i++){
        int MIN=inf, u=-1;
        for(int j=0; j<n; j++){
            if(vis[j]==false && d[j]<MIN){
                MIN=d[j];
                u=j;
            }
        }
        if(u==-1) return;
        vis[u]=true;
        for(int v=0; v<n; v++){
            if(vis[v]==false && G[u][v]!=0){
                if(d[v]>d[u]+G[u][v]){//更新最短路径
                    d[v]=d[u]+G[u][v];
                    pre[v].clear();
                    pre[v].push_back(u);
                }
                else if(d[v]==d[u]+G[u][v]){
                    pre[v].push_back(u);
                }
            }
        }
    }
}

DFS代码可以修改为:

//增加变量
//path为最优路径、temp临时路径
vector<int> path, tempPath;
int optValue;//第二指标最优值
void DFS(int v, int s){//终点和起点
    //边界条件:计算第二指标,比较替换保存到最优路径
    if(v==s){
        tempPath.push_back(v);//把最后一个点推入路径
        int value;//新建指标值
        计算路径tempPath的value;
        if(value优于optValue){//如果更优更新路径
            optValue = value;
            path = tempPath;
        }
        tempPath.pop_back();//删除刚加入的节点
        return;
    }
    //递归式
    tempPath.push_back(v);
    for(int i=0; i<pre[v].size(); i++){
        DFS(pre[v][i],s);
    }
    //遍历完所有前驱节点,将当前节点删除,☆☆☆☆
    tempPath.pop_back();
}

三、Floyd算法jiu

未完待续,先做题。。。

猜你喜欢

转载自blog.csdn.net/Dirichlet_zju/article/details/84576426