Dijkstra+DFS模板总结

来源:互联网 发布:js数组和对象 编辑:程序博客网 时间:2024/05/22 01:31

关于Dijstra的初级运用是,在第一标尺的基础上有下面三个角度:

  • 边权:c[maxn] = {maxn}, cost[manx][maxn] = {inf};
  • 点权:w[maxn] = {0}, weight[maxn] = {0};
  • 最短路径条数:num[maxn] = {0};

a1003.cpp 用到了其中的两个,作为模板来刻意练习,练习如何将问题结构化,模板化。

再额外补充边权的代码,不是这道题的,但是加进来使其完整。

#include <stdio.h>#include <string.h>#include <algorithm>using namespace std;const int maxv = 510; // 最大顶点数const int inf = 1 << 30;int G[maxv][maxv], weight[maxv],cost[maxv][maxv]; // 定义图的邻接矩阵存法,点权int d[maxv] = {inf} ; //记录从起点s到u的最短距离,int n,m,st,ed; // n:顶点数,m:边数int w[maxv],num[maxv]c[manv]; // w:记录最大点权之和,num:最短路径条数bool vis[maxv] = {false}; // 标记是否已经访问// 最朴素的迪杰斯塔拉就是为了找到最短路径,最终效果是更新了d数组,// 但是在产生d数组的过程中,有意思的事情正在发生void Dijkstra(int s) {    // 初始化    fill(d,d + maxv, inf); //     fill(num,num + maxv, 0);    fill(w,w + maxv, 0);    d[s] = 0;    w[s] = weight[s];     num[s] = 1;     // 循环n次    for(int i = 0; i < n; i++)    {        // 找到u和最小值        int u = -1, MIN = inf;        for(int j = 0; j < n; j++)        {            if(vis[j] == false && d[j] < MIN)            {                u = j;                MIN = d[j];            }        }        if(u == -1) return; // 没有找到        vis[u] = true; // 找到了就标记为已经访问        // 优化路径长度        for(int v = 0; v < n; v++)        {            if(vis[v] == false && G[u][v] != inf)            {                if(d[u] + G[u][v] < d[v]) // 这个条件拿下来用                {                    d[v] = d[u] + G[u][v]; // 覆盖                                        c[v] = c[u] + cost[u][v]; // 补充的边权                    w[v] = w[u] + weight[v]; // 最短路径的权重更大                    num[v] = num[u]; // 三个基础问题中唯一一个用到继承的概念的                }                else if(d[u] + G[u][v] == d[v]) // 最短路径有相同的,这时候就看点权之和                {                    if(w[u] + weight[v] > w[v])                    {                        w[v] = w[u] + weight[v];                    }                                        if(c[u] + cost[u][v] < c[v])                                        {                                                c[v] = c[u] + cost[u][v];                                        }                    num[v] += num[u];                 }            }        }    }}int main(){       scanf("%d%d%d%d", &n, &m, &st, &ed);    for(int i = 0; i < n; i++)    {        scanf("%d",&weight[i]);    }    int u, v;    fill(G[0],G[0] + maxv * maxv, inf);// 初始化二维数组的写法    for(int i = 0; i < m; i++)    {        scanf("%d%d", &u, &v);        scanf("%d", &G[u][v]);//读入边权        G[v][u] = G[u][v];    }    Dijkstra(st);    printf("%d %d\n",num[ed],w[ed]);    return 0;}

为了理解Dijkstra + DFS,需要首先消化掉只在第一标尺下的DFS与记录前驱的pre数组的方式,然后才能更好的理解多个标尺下的Dijkstra + DFS的思路。

void DFS(int s, int v) // s是起点编号, v是当前访问的顶点编号,这个递归函数目的是为了求s到v的路径{    // 前提是pre数组准备好,pre[i] = i,初始化时是自身是自身的前驱(联想到并查集啦)    // 递归边界    if(v == s)    {        printf("%d\n", s);        return;    }    DFS(s,pre[v]);    printf("%d\n",v);}

单纯的用递归函数的设计逻辑来理解这个问题,就会很简单:

  • 递归边界:v == s,即起点和终点重合了,自然要输出,并结束本层函数
  • 递归式:起点是固定的,是fixed,第二个函数是终点往前挪到前驱
  • 本层逻辑:递归回来,要输出当前这个结点的编号

用Dijkstra + DFS组合解题的情景是解脱出在Dijikstra时要处理的逻辑较为复杂,这里的说的复杂逻辑不是DIjkstra本身,Dijkstra的框架是非常简洁优美且代码好写的。

如果只在Dijkstra中记录所有最短路径,然后再在DFS中根据其他标尺求出最优路径,这种做法也非常符合关注点分离的思想。

所以,首先看第一个问题:如何在Dijkstra中记录所有最短路径。

vector<int> pre[maxn] // 存储多条最短路径:仅在第一标尺--距离的指引下

在这种记录多条最短路径的pre数组中,开始不用初始化,看到代码会更清晰:

if(d[u] + G[u][v] < d[v]){    d[v] = d[u] + G[u][v];    pre[v].clear(); // 清空,此时找到更好的了    pre[v].push_back(u); } else if(d[u] + G[u][v] == d[v]){    pre[v].push_back(u); // 令v的前驱为u}

关于为什么在找到更小的距离时清空pre[v]数组,是因为pre[v]存的是v这个顶点距离最小的前驱,那么现在找到了更小的,意味着原来的记录的前驱没用了,自然清空。也因为此,pre数组开始不必初始化。

现在数组已经准备好,开始写DFS遍历所有最短路径,依据其他标尺选择最优。

int optValue;vector<int> pre[maxv];vector<int> path, tempPath;void DFS(int v){    if(v == st) // st是起点    {        tempPath.push_back(v); // 将起点st加入临时路径tempPath的最后面        int value; // 存放临时路径的第二标尺值        // 计算tempPath上的value值        if(value 优于 optValue)        {            optValue = value;            path = tempPath;        }        tempPath.pop_back();        return;    }    tempPath.push_back(v); // 将当前访问结点加入临时路径tempPath最后面    for(int i = 0; i < pre[v].size(); i++)    {        DFS(pre[v][i]);    }    tempPath.pop_back();// 遍历完所有前驱结点,将当前结点v删除}

注意到push_back和pop_back是成对出现的这里。

这是形式上的准确识别记忆,那么如何理解呢?

其实非常简洁,tempPath存储的是一条路径 ,即需要考察起点st到当前结点之间的某个标尺值,tempPath因为是采用递归写法,所以是倒着的:从v到st。

本层逻辑是先把v加入进来,然后递归v的前驱,刚好前驱是往前(向着起点)走。我们先忽略掉递归式,看到最后一句pop_back,这样就能保证执行完一次递归获得一条路径后,tempPath被清空。

而至于递归边界中的一对,是因为本层逻辑中无法把起始点加入tempPath,所以这里需要临时用到,所以临时加进来。

这样Dijkstra + DFS模板的问题框架与部分细节就搭起来了,再来看最初提到的三个基础问题这里如何计算:

  • 点权之和
  • 边权之和
  • 最小路径条数

最简单的是最小路径条数:用一个全局变量num = 0, 在递归边界中num++即可。
点权之和与边权之和都是上面模板中的value,具体写法也很简单,就是遍历tempPath数组。注意,path是存的几种标尺综合的最优解。

// 边权之和:注意是i > 0int value = 0;for(int i = tempPath.size() - 1; i > 0; i++){    int u = tempPath[i], v = tempPath[i - 1];    value += G[u][v];// G[u][v]是边权}// 点权之和int value = 0;for(int i = tempPath.size() - 1; i >= 0; i++){    int u = tempPath[i];    value += weight[u];// weight是边权}
0 0
原创粉丝点击