最多使用k个顶点的有向加权图中的最短路径

时间:2016-10-20 14:58:29

标签: c++ algorithm graph shortest-path dijkstra

我正在尝试解决具有非负权重的连通有向加权循环图中的SSSP问题。这里的问题是,这个问题要求最多使用k个顶点的SSSP。示例图如下所示:


enter image description here

现在,使用最多2个顶点的从0到3的最短路径为0到3,成本为7.

这个问题的界限非常大:

Time limit: 1s
2 <= No. of Vertices <= 1,000
No. of Edges <= 200,000
No. of Queries = 20
Value of k = min(V,20)

我尝试使用修改后的dijkstra算法来解决这个问题,在我的优先级队列中保留一个3元组。即(顶点权重,到该顶点的路径中的顶点数(包括),顶点索引)。我的算法可以防止多于k个顶点的节点被推入优先级队列,从而被认为是在最短路径中。

不知何故,我的算法得到了错误的答案。一个原因是,如果最初较小的加权边缘导致无效路径并且最初较大的加权边缘导致有效路径,则我的算法(贪婪)将报告它无法找到到目的地的有效路径。 / p>

我的解决方案如下:

#include<cstdio>
#include<queue>
#include<vector>
#include<utility>

using namespace std;

int V = 0,Q = 0;
vector < vector < pair<int,int>  > > AdjList; 
priority_queue<pair<int,pair<int,int> >, vector<pair<int,pair<int,int> > >,greater<pair<int,pair<int,int> > > > min_queue;
const int INF = 1 << 30;

int solve(int s, int t, int k) {
    // reset priority queue
    while(!min_queue.empty()) min_queue.pop();
    min_queue.push(pair<int,pair<int,int> >(0,pair<int,int>(1,s)));
    vector<int> dist;
    dist.assign(V,INF);
    dist[s] = 0;
    vector<int> steps;
    steps.assign(V,INF);
    steps[s] = 1; // source vertex counts as first step

    // use modified dijkstra's algorithm (lazy update version)
    while(!min_queue.empty()) {
        // Get the smallest element in the priority queue
        pair<int,pair<int,int> > minE = min_queue.top();
        min_queue.pop();
        int u = minE.second.second, uw = minE.first, uh = minE.second.first;
        // prune outdated copies of edges from the queue
        if(dist[u] == uw && steps[u] == uh) {
            // stop immediately when destination is reached
            if(u==t) return uw;
            // Shortest path with at most k steps only
            if(uh < k) {
                // Get list of neighbours
                vector<pair<int,int> > adj = AdjList[u];
                for(int i = 0; i < adj.size(); i++) {
                    int v = adj[i].first, vw = adj[i].second;
                    // relax steps and weights of neighbouring edges
                    if(dist[v] > dist[u] + vw) {
                        dist[v] = dist[u] + vw;
                        steps[v] = steps[u] + 1;
                        min_queue.push(pair<int,pair<int,int> >(dist[v],pair<int,int>(steps[v],v)));
                    }
                }
            }
        }
    }
    return -1; // There is no valid path that uses at most k steps
}

int main(void) {
    int cases = 0, v = 0, w = 0, a = 0, b = 0, c = 0;
    scanf("%d",&cases);

    while (cases-- > 0) {
        scanf("%d",&V);

        // clear the graph and read in a new graph as Adjacency List
        AdjList.clear();
        for (int i = 0; i < V; i++) {
            AdjList.push_back(vector < pair<int,int> >());

            int k = 0;
            scanf("%d",&k);
            while (k-- > 0) {
                scanf("%d %d",&v,&w);
                AdjList[i].push_back(pair<int,int>(v, w));
            }
        }

        scanf("%d",&Q);
        while (Q-- > 0) {
            scanf("%d %d %d",&a,&b,&c);
            printf("%d\n",solve(a,b,c));
        }

        if (cases > 0)
            printf("\n");
    }
    return 0;
}

我不确定如何纠正我的解决方案。此外,时间限制令我头疼。有人可以帮助我吗?

编辑(解决方案):

我设法解决了这个问题。解决方案是使用2D矩阵定义为:

dist [u] [h + 1]:u是目标顶点,h是到达该顶点的步数。大小h + ​​1是由于源顶点本身已经是1步。

这是因为最短路径取决于目标顶点和步骤数。但请注意,此方法仅有效,因为包含的最大顶点数k很小(&lt; = 20)。

为了可能面临同样问题的其他人的利益。下面给出了正确的C ++解决方案:

#include<cstdio>
#include<queue>
#include<vector>
#include<utility>

using namespace std;

int V = 0,Q = 0;
vector< vector< pair<int,int>  > > AdjList; 
priority_queue< pair< int,pair<int,int> >, vector< pair<int,pair<int,int> > >,greater< pair< int,pair<int,int> > > > min_queue;
const int INF = 1 << 30;

// edges are represented as a triple (weight,steps taken,destination vertex)

int solve(int s, int t, int k) {
    // reset priority queue
    while(!min_queue.empty()) min_queue.pop();
    // Initialize priority queue with source node
    min_queue.push(pair< int,pair<int,int> >(0,pair<int,int>(1,s)));
    vector< vector<int> > dist;
    for(int i = 0; i < V; i++) {
        dist.push_back(vector<int>());
        dist[i].assign(k+1,INF);
    }
    dist[s][1] = 0;

    // use modified dijkstra's algorithm (lazy update version)
    while(!min_queue.empty()) {
        // Get the smallest element in the priority queue
        pair<int,pair<int,int> > minE = min_queue.top();
        min_queue.pop();
        int u = minE.second.second, uw = minE.first, uh = minE.second.first;
        // prune outdated copies of edges from the queue
        if(dist[u][uh] == uw) {
            // stop immediately when destination is reached
            if(u==t) return uw;
            // Shortest path with at most k steps only
            if(uh < k) {
                // Get list of neighbours
                vector<pair<int,int> > adj = AdjList[u];
                for(int i = 0; i < adj.size(); i++) {
                    int v = adj[i].first, vw = adj[i].second;
                    // relax steps and weights of neighbouring edges
                    if(dist[v][uh+1] > dist[u][uh] + vw) {
                        dist[v][uh+1] = dist[u][uh] + vw;
                        min_queue.push(pair<int,pair<int,int> >(dist[v][uh+1],pair<int,int>(uh+1,v)));
                    }
                }
            }
        }
    }
    return -1; // There is no valid path that uses at most k steps
}

int main(void) {
    int cases = 0, v = 0, w = 0, a = 0, b = 0, c = 0;
    scanf("%d",&cases);

    while (cases-- > 0) {
        scanf("%d",&V);

        // clear the graph and read in a new graph as Adjacency List
        AdjList.clear();
        for (int i = 0; i < V; i++) {
            AdjList.push_back(vector < pair<int,int> >());

            int k = 0;
            scanf("%d",&k);
            while (k-- > 0) {
                scanf("%d %d",&v,&w);
                AdjList[i].push_back(pair<int,int>(v, w));
            }
        }

        scanf("%d",&Q);
        while (Q-- > 0) {
            scanf("%d %d %d",&a,&b,&c);
            printf("%d\n",solve(a,b,c));
        }

        if (cases > 0)
            printf("\n");
    }
    return 0;
}

2 个答案:

答案 0 :(得分:1)

我发现很难读取你的代码 - 所以也许你已经这样做了:给每个顶点一组最佳路径(编辑:实际上每个顶点只存储每个可能路径的前一步),为一定数量的访问顶点存储最便宜的,一旦路径超过最大顶点数,就可以丢弃它,但是在知道更便宜的路径之前,不能丢弃更昂贵的(就总边长度而言)路径最终将以可接受数量的顶点到达目标。 最后你可能有不止一条完整的路径,你只需要选择边缘最少的,不管顶点的数量是多少(如果有太多顶点,你已经丢弃了它)

(如果你创建一个类/结构对于你作为一对对等存储的东西,你的代码会更容易阅读,那么你可以给成员更清晰的名字而不是second.first等。即使你对于当前的命名是否正常,如果上述内容没有帮助,额外的清晰度可能会帮助您获得其他答案。)

编辑回答:“在我知道更便宜的路径会导致解决方案之前,我如何保留更昂贵的路径?”

您的优先级队列几乎已经这样做了 - 并不是每个顶点(n)都有一个完整的路径存储,就像我最初暗示的那样,目前您只需存储最佳的前一个顶点(n-1)来用于到达顶点n - 及其成本和顶点数。我是说,不是存储顶点n-1的最佳选择,而是存储多个选项,例如:使用3个先前顶点到A的最佳路径是从顶点B和成本12,使用4的最佳路线是从顶点C和成本10。 (以上所有最佳方法是迄今为止在搜索中找到的最佳方法)

您只需要为给定数量的顶点存储最便宜的路线。如果(但仅限于)它在成本或顶点数上保持更好的路线。

在上面的示例中,您需要保留两者,因为到此顶点的更便宜的路径使用更多的先前顶点,因此在到达目标之前可能会导致太多顶点 - 在该阶段不清楚哪条路径最终会是最佳的。

因此,您需要更改收藏类型以及丢弃选项的规则。 例如,您可以使用std :: map,其中前一个顶点计数是关键,总边缘成本和前一个顶点ID存储在值中,或者是一个总成本数组,其中index是计数。

答案 1 :(得分:0)

我认为你想为每个节点存储两个增量器:一个用于节点计数,一个用于加权距离。您使用node-count作为早期终止符来从潜在选项集中丢弃这些路径。使用加权距离选择要迭代的下一个节点,并根据节点计数进行丢弃。通过这种方式,如果您完全终止外围设备上的所有节点是可丢弃的,那么您就知道没有符合条件的路径到目的地,最多只能达到所需的跳数。如果你到达外围节点列表中的目的地,那么你自动知道它不超过有限数量的节点,并且通过归纳你知道它已经是到达那里的最短路径,因为每个其他路径都可以从然后必须已经有更长的路径了。

相关问题