Bellman Ford算法在一个未知的测试用例中失败

时间:2017-01-17 06:32:02

标签: algorithm graph bellman-ford

我正在为过去的一门课程设置问题。我应该实现Bellman Ford算法,这样从源s,我必须找到以下内容:

  1. 如果节点无法从s(输出为*
  2. 无法访问
  3. 如果节点可以访问但是是负循环的一部分,因此没有最短路径(输出为-
  4. 否则,输出从s到节点
  5. 的最短路径

    我编写了以下代码,但在未知的测试用例中失败了。有人可以帮我调试吗?

    void relax_edges(vector <vector<int>> &adj, 
                     vector <vector<int>> &cost, 
                     vector<long long> &dis) 
      {
      /*Takes input as adjacency list and relax all possible edges
      */
    
      for (int i = 0; i < adj.size(); i++) {
        for (int j = 0; j < adj[i].size(); j++) {
          if (dis[i] < std::numeric_limits < long long > ::max() 
                 && dis[adj[i][j]] > dis[i] + cost[i][j]){
            //std::cout<< adj[i][j]<<" "<<i<<"\n";
            dis[adj[i][j]] = dis[i] + cost[i][j];
          }
        }
      }
    }
    
    void bfs(vector<vector<int> > &adj, vector<int>& shortest, int s){
        vector<int> seen (adj.size(), 0);
        //seen[s] = 1;
        queue<int> q;
        q.push(s);
        int t;
        while(!q.empty()){
            t = q.front();
            q.pop();
            if (seen[t] == 3)
                break;
            seen[t]+=1;
            for(int i=0;i<adj[t].size();i++){
                q.push(adj[t][i]);
            }
        }
    
        for(int i=0;i<seen.size();i++)
            if(seen[i]>=1)
                shortest[i] = 0;
    }
    
    void shortest_paths(vector <vector<int>> &adj,
                        vector <vector<int>> &cost,
                        int s,
                        vector<long long> &dis,
                        vector<int> &reachable,
                        vector<int> &shortest) {
    
      dis[s] = 0;// distance of s is 0 from s      
      int result;
      for (int i = 0; i < adj.size() - 1; i++) { //Running Bellman Ford |V-1| times
        relax_edges(adj, cost, dis);
      }
      vector<long long> distance2(dis.size(), 0);
      for (int i = 0; i < distance2.size(); i++)
        distance2[i] = dis[i];
    
      relax_edges(adj, cost, distance2); // Running it |V|th time to identify negative cycle nodes
      relax_edges(adj, cost, distance2); //Running it |V+1|th time to identify the first node of the negative cycle.
    
    
      for(int i=0;i<distance2.size();i++){
        //std::cout<<distance2[i]<<" "<<dis[i]<<"\n";
        if(distance2[i]!=dis[i]){
            bfs(adj, shortest, i);
            shortest[i] = 0;
        }
        if (dis[i] < std::numeric_limits<long long>::max())
            reachable[i] = 1;       
      }
    }
    

    问题是我甚至无法识别它失败的测试用例。

2 个答案:

答案 0 :(得分:5)

我可以为您提供实施失败的示例。我还将描述问题的解决方案。

运行relax_edges |V| - 1次后,您可以找到来自源的所有有效最短路径,假设没有负循环。在放松|V|次之后,您可以确定是否可以从源(任何最短距离减少)到达任何负周期。但是,对于每个节点来说,这还不足以说明它是否位于可达到的负循环中。

查看下面的示例(无限远的INF个状态。)

An example for which the implementation fails

正如您所看到的,在放松|V|之后,我们可以说从源(节点3)可以得到负循环。我们知道因为有来自源的最短路径刚刚减少的节点(23)。但是,经过两次额外的放松(如在您的实现中),我们仍然不知道节点0处于负循环。距离源的最短距离仍然与|V| - 1松弛相同。

请注意以下事项。如果一个节点处于某个负循环,它也处于长度最多为|V|的负循环。无论您的情况如何,对于简单的图形和多图形都是如此。因此,不应在算法的第二部分中运行relax_edges一次或两次,而应运行|V|次。这样,您可以确保每个节点都可以遍历包含它的整个负循环。在这些|V|次额外放松之后,您可以将得到的最短距离与您在第一次|V| - 1放松时得到的距离进行比较。如果任何节点的最短距离较低,那么您可以确定它处于从源可到达的负循环。

当然,所提供的解决方案不会增加仍为O(| V | * | E |)的时间复杂度。唯一的缺点是更高的常数。

在问题陈述中,您还提到需要返回最短路径(如果存在)。但是,我无法在您提供的代码中看到它的相关部分。如果这也是一个缺失的东西,你只需要在放松期间更新距离时记住节点的前任。您可以在Wikipedia上找到更新前辈的简单示例。如果您有最短路径存在的每个节点的前驱,则可以向后沿路径直到到达源。

编辑:

@ead表明我的字面意思是“2”,所以我正在改进我的答案。我提出的算法可以告诉每个节点它是否是从源可到达的负循环的一部分。但是,可能有一个节点本身不是任何负循环的一部分,但您可以从源可到达的负循环到达此节点。因此,要说存在到某个节点的最短路径,您必须确保从源可到达的任何负循环都无法访问它。

正如@ead所提出的,一个非常合理的解决方案是从|V| - 放松期间被检测为负循环一部分的所有节点运行图搜索(DFS或BFS)。例如,每当执行shortest[i] = 0时,您还会将节点i添加到队列中。然后,您可以运行具有多个起始节点的单个BFS,这些节点已存在于队列中。在此BFS期间访问的每个节点都是从源可到达的负循环的一部分,因此没有最短的路径。

编辑2:

BFS的方法通常是正确的,但我认为你的实现是错误的。我无法确定是否seen[t] == 3。您只需要记住每个节点是否在图搜索期间访问过。等效地,每当节点被推送到队列时,您都可以将其标记为seen。这是BFS的固定版本:

void bfs(vector<vector<int>>& adj, vector<int>& shortest, int s) {
    vector<int> seen(adj.size(), 0);

    queue<int> q;
    q.push(s);
    seen[s] = 1;

    while (!q.empty()) {
        int t = q.front();
        q.pop();

        for (int i = 0; i < adj[t].size(); ++i) {
            if (!seen[adj[t][i]]) {
                q.push(adj[t][i]);
                seen[adj[t][i]] = 1;
            }
        }
    }

    for (int i = 0; i < seen.size(); ++i) {
        if (seen[i] > 0) {          
            shortest[i] = 0;
        }
    }
}

我还建议在shortest_paths函数内创建队列,并向每个节点推送满足distance2[i] != dis[i]的节点。然后,您需要使用初始化队列运行BFS一次。

答案 1 :(得分:2)

我认为你误解了这个问题。在我看来2.应该是:

  

如果节点可以到达 的区别是可达到的负循环,因此没有最短路径(输出为 - )

因为很明显,人们可以跑到负循环,进行任意数量的循环而不是跑到你的目的地。

第二个问题是,|V|+1的{​​{1}}次迭代甚至不足以找到负循环中的所有节点。

解决问题的一种可能方法是:

  1. 运行relax_edges
  2. |V|-1次迭代
  3. 再运行一次relax_edges的迭代,找到负循环的一些节点。这是有保证的,对于每个负循环,您将检测至少一个节点。
  4. 从这些检测到的节点运行bfs或dfs,以查找从负循环可到达的所有节点(没有明确定义到这些节点的最短路径)。
  5. 修改:以下示例显示,@ Ardavel建议relax_edgerelax_edges运行不起作用:

    enter image description here

    在运行2*|V| 8次后,人们会认为,从1到4的最短路径直接占据边缘relax_edges。然而,这不是最短的路径,因为一个可以运行到2,而不是在负循环中运行超过2000次(2 - 3)并且只是将出口运行到4。

    要检测到这一点,您必须执行1->4次超过2000次!

    这是你在负循环中检测到第一个节点后应该做你喜欢的bfs / dfs /其他算法的原因。