求图中两点间的所有的最短路径

一、理论篇

\quad 如下图所示,是一个有7个顶点的图,每条边权值均为1,试问从点0点6,有多少条最短路径呢,分别是什么?
求图中两点间的所有的最短路径_第1张图片
\quad 我们可以直观的看出来,一共有4条最短路径,分别是

  • 0->1->4->6
  • 0->2->4->6
  • 0->2->5->6
  • 0->3->5->6

\quad 那么问题来啦,我们要如何记录下这些所有的最短路径呢,以前在只需记录一条最短路径的情况下,我们只需要一个数组来记录当前节点的前驱节点是什么,最后再通过不断的获取当前节点的前驱节点来获得路径。在记录所有的最短路径时,我们需要建立一个二维数组,用于存储当前节点可以由哪些节点得到,用最短路算法得到这样一个二维数组后,我们便可以通过DFS得到所有路径。
\quad 具体而言,针对上面的图,我们建立一个二维数组vector pre[7]来存放0到每个节点的最短路可以由哪些前驱节点得到,那么可知

  • pre[0] = {}
  • pre[1] = {0}
  • pre[2] = {0}
  • pre[3] = {0}
  • pre[4] = {1, 2}
  • pre[5] = {2, 3}
  • pre[6] = {4, 5}

\quad 这样,我们对pre进行dfs,就可以得到这样一棵递归树。这棵树就是我们的解空间。
求图中两点间的所有的最短路径_第2张图片

二、代码篇

\quad 现在我们来一步一步写程序实现这个求解过程,主要分为两步,第一步是用Dijistra得到pre数组,第二步是对pre进行DFS获得所有路径。首先,我们看看在Dijistra的最优子结构中,pre如何得到和更新。

if(dis[v] > dis[u] + G[u][v])
{
	dis[v] = dis[u] + G[u][v];
	pre[v].clear();  //这里要记得clear
	pre[v].push_back(u);
}
else if(dis[v] == dis[u] + G[u][v]) 
{
	 pre[v].push_back(u);
}

\quad 如果 dis[u] + G[u][v] < dis[v], 说明以u为中介点可以使 dis[v]更优,此时需要令v的前驱结点为u。并且即便原先 pre[v]中已经存放了若干结点,此处也应当先清空,然后再添加u。之后,如果 dis[u] + G[u][v] = dis[v], 说明以 u 为中介点可以找到一条距离相同的路径,因此v的前驱结点需要在原先的基础上添加上 u 结点(而不必先清空pre[v])。完整的Dijistra如下,我用的是队列优化的Dijistra,不优化的话直接将上述最优子结构加在普通的Dijistra上就行:

#include 
#include 
#include 
#include 
#include 
using namespace std;

const int maxn = 1e4+10;
vector<pair<int, int> > E[maxn];
vector<int> pre[maxn], temp;
int vis[maxn], dis[maxn];
void dijstra(int s)
{
    fill(dis, dis+maxn, 0x3f3f3f3f);
    dis[s] = 0;
    priority_queue<pair<int, int> > q;
    q.push(make_pair(0, s));
    while(!q.empty())
    {
        int u = q.top().second;
        q.pop();
        if(vis[u]==1) continue;
        vis[u] = 1;
        for (int i = 0; i < E[u].size(); ++i)
        {
            int v = E[u][i].first, w = E[u][i].second;
            if(dis[v]>dis[u]+w)
            {
                dis[v] = dis[u]+w;
                pre[v].clear();
                pre[v].push_back(u);
                if(vis[v]==0) q.push(make_pair(-dis[v], v));
            }
            else if(dis[v]==dis[u]+w)
            {
                pre[v].push_back(u);
                if(vis[v]==0) q.push(make_pair(-dis[v], v));
            }
        }
    }
}
int main(int argc, char const *argv[])
{
    int N, M, s, t;  // 点数,边数,起点,终点
    cin >> N >> M >> s >> t;
    while(M--)
    {
        int u, v, w;
        cin >> u >> v >> w;
        E[u].push_back(make_pair(v, w));
        E[v].push_back(make_pair(u, w));
    }
    dijstra(s);
    for (int i = 0; i < N; ++i)
    {
        printf("pre[%d]: ", i);
        for (int j = 0; j < pre[i].size(); ++j)
        {
            printf("%d ", pre[i][j]);
        }
        printf("\n");
    }
    return 0;
}
/*
输入
7 9 0 6
0 1 1
0 2 1
0 3 1
1 4 1
2 4 1
2 5 1
3 5 1
4 6 1
5 6 1
*/

求图中两点间的所有的最短路径_第3张图片
\quad 第二部分是对pre进行dfs,获得所有的最短路径。我们从终点t开始dfs,当终点t等于起点s时输出路径,否则取出其前驱节点继续dfs。在程序里面我将注释写得很清楚啦

void dfs(int s, int t)
{
    if(s==t) // 当到达起始节点,表明找到啦一条路径,输出
    {
        temp.push_back(t);
        // 输出路径,若不想输出可以用一个二维vector存储每条路径,注意是倒序
        for (int i = temp.size()-1; i >= 0; i--){
            cout << temp[i] << " ";
        }
        cout << endl;
        temp.pop_back(); // 将刚加入的节点删除
        return;
    }
    temp.push_back(t);  // 将当前访问的节点加入临时路径temp后面
    for (int i = 0; i < pre[t].size(); ++i){
        dfs(s, pre[t][i]);
    }
    temp.pop_back();  // 遍历完v点所有的前驱节点,将v删除
}

\quad 总的求解程序如下

#include 
#include 
#include 
#include 
#include 
using namespace std;

const int maxn = 1e4+10;
vector<pair<int, int> > E[maxn];
vector<int> pre[maxn], temp;
int vis[maxn], dis[maxn];
void dijstra(int s)
{
    fill(dis, dis+maxn, 0x3f3f3f3f);
    dis[s] = 0;
    priority_queue<pair<int, int> > q;
    q.push(make_pair(0, s));
    while(!q.empty())
    {
        int u = q.top().second;
        q.pop();
        if(vis[u]==1) continue;
        vis[u] = 1;
        for (int i = 0; i < E[u].size(); ++i)
        {
            int v = E[u][i].first, w = E[u][i].second;
            if(dis[v]>dis[u]+w)
            {
                dis[v] = dis[u]+w;
                pre[v].clear();
                pre[v].push_back(u);
                if(vis[v]==0) q.push(make_pair(-dis[v], v));
            }
            else if(dis[v]==dis[u]+w)
            {
                pre[v].push_back(u);
                if(vis[v]==0) q.push(make_pair(-dis[v], v));
            }
        }
    }
}
void dfs(int s, int t)
{
    if(s==t) // 当到达起始节点,表明找到啦一条路径,输出
    {
        temp.push_back(t);
        // 输出路径,若不想输出可以用一个二维vector存储每条路径
        for (int i = temp.size()-1; i >= 0; i--){
            cout << temp[i] << " ";
        }
        cout << endl;
        temp.pop_back(); // 将刚加入的节点删除
        return;
    }
    temp.push_back(t);  // 将当前访问的节点加入临时路径temp后面
    for (int i = 0; i < pre[t].size(); ++i){
        dfs(s, pre[t][i]);
    }
    temp.pop_back();  // 遍历完v点所有的前驱节点,将v删除
}
int main(int argc, char const *argv[])
{
    int N, M, s, t;
    cin >> N >> M >> s >> t;
    while(M--)
    {
        int u, v, w;
        cin >> u >> v >> w;
        E[u].push_back(make_pair(v, w));
        E[v].push_back(make_pair(u, w));
    }
    dijstra(s);
    dfs(s, t);
    return 0;
}

/*
7 9 0 6
0 1 1
0 2 1
0 3 1
1 4 1
2 4 1
2 5 1
3 5 1
4 6 1
5 6 1
*/

\quad 运行结果如下:
求图中两点间的所有的最短路径_第4张图片

你可能感兴趣的:(ACM)