为啥这个 Dijkstra 算法不适用于这个特定的输入?

Posted

技术标签:

【中文标题】为啥这个 Dijkstra 算法不适用于这个特定的输入?【英文标题】:why this Dijkstra algorithm not working for this particular input?为什么这个 Dijkstra 算法不适用于这个特定的输入? 【发布时间】:2017-09-30 21:06:50 【问题描述】:

我已经实现了Dijkstra's algorithm。 但是当输入如下时它不起作用:

1
6 7
1 2 5
2 3 2
3 4 3
1 4 9
4 5 2
5 6 3
1 6 2
1

我在调试模式下运行它,以了解问题所在。似乎节点 5 没有插入单元格中。我不知道为什么。

代码如下:

#include<iostream>
#include<vector>
#include<list>
#include <limits>
#include <algorithm>
#include <queue>
#include <set>
#include <stack>
using namespace std;

struct compare

    bool operator()(pair<int, int> a, pair<int, int> b) const
    
        return a.second < b.second;
    

;

void printResults(vector<int> vec, int starting)

    for (int i = 1; i < vec.size(); i++)
    
        if (vec[i] == numeric_limits<int>::max() && i != starting)
        
            cout << -1 << " ";
        
        else if (i != starting)
        
            cout << vec[i] << " ";
        
    


void djkstra(vector<vector<pair<int, int>>>&vec, int starting, int number_of_vertices)

    int max = numeric_limits<int>::max();
    set <pair<int, int>,compare> queue;
    vector<bool> visited(number_of_vertices + 1, false);
    vector<int> distances(number_of_vertices + 1, max);
    vector<int> parents(number_of_vertices + 1, -1);
    queue.insert(make_pair(starting, 0));
    distances[starting] = 0;
    for (int i = 0; i<number_of_vertices-1; i++)
    
        pair<int, int> minElem = *queue.begin(); //take min elem
        queue.erase(minElem);
        vector<pair<int, int>> adjacents = vec[minElem.first];//take neighbours
        for (int j = 0; j<adjacents.size(); j++)
        
            pair<int, int> node = adjacents[j];
            if (!visited[node.first])
            
                if (distances[node.first]> distances[minElem.first] + node.second) //if we have found smaller distance
                

                    if (queue.find(make_pair(node.first, distances[node.first])) != queue.end())
                    
                        queue.erase(make_pair(node.first, distances[node.first]));
                        queue.insert(make_pair(node.first, distances[minElem.first] + node.second));

                    
                    else
                    
                        queue.insert(make_pair(node.first, distances[minElem.first] + node.second));
                        cout<<distances[minElem.first] + node.second<<endl;

                    

                    distances[node.first] = distances[minElem.first] + node.second;
                

            

        
        visited[minElem.first] = true;

    
    printResults(distances,starting);



int main()

    int test;
    cin >> test;
    for (int i = 0; i < test; i++)
    
        int nodes, edges;
        cin >> nodes >> edges;
        vector<vector<pair<int, int>>> vec(nodes + 1);
        for (int j = 0; j < edges; j++)
        
            int src, des, weight;
            cin >> src >> des >> weight;
            vec[src].push_back(make_pair(des, weight));
            vec[des].push_back(make_pair(src, weight));

        

        int starting;
        cin >> starting;
        djkstra(vec, starting, nodes);

    

    system("pause");

    return 0;

【问题讨论】:

当您使用调试器单步调试时,5 去了哪里? 它没有添加到队列中。所以第一步队列有节点1,之后它有节点2,4,6,那么最小节点是6,它必须将节点5添加到排队,但它没有这样做 输入短:coliru.stacked-crooked.com/a/5fb33d0267af082f @sehe 你是对的。应该是多行,你指定起始节点的地方。在这种情况下它是 1 if (distances[node.first] &gt; distances[minElem.first] + node.second) - 这比较......距离(距离+顶点id)?!此外,当邻接列表中已知时,您的 ajdacency 列表会重复每个传出边的起始顶点 【参考方案1】:

所以,after 尝试将 to 制作成 sense of 你的 question code,我根据 the Wikipedia Pseudo-code 进行了干净的重写

认为(因为你仍然没有显示你得到的输出,这应该是错误的),你的主要错误只是使用set&lt;&gt;

Bugged Version

#include <algorithm>
#include <iostream>
#include <limits>
#include <list>
#include <queue>
#include <set>
#include <stack>
#include <vector>
#include <cassert>

using Vertex  = unsigned;
using Weight  = double;

struct OutEdge 
    Vertex node;
    Weight weight;
    bool operator<(OutEdge const& o) const  return weight < o.weight; 
;

using OutEdges     = std::vector<OutEdge>;
using AdjList      = std::vector<OutEdges>;
using Distances    = std::vector<Weight>;
using Predecessors = std::vector<Vertex>;

static Weight const INFINITY = std::numeric_limits<Weight>::max();
static Vertex const UNDEFINED -1u;

using namespace std;

void print_graph(AdjList const& graph) 
    for (Vertex v = 1; v < graph.size(); v++) 
        std::cout << v << " -";
        for (auto& adj : graph[v])
            std::cout << " " << adj.node << "(" << adj.weight << ")";
        std::cout << "\n";
    


void printResults(Distances const& dist, Vertex starting) 
    for (Vertex v = 0; v < dist.size(); v++) 
        std::cout << starting << " -> " << v << ": ";
        if (dist[v] == INFINITY && v != starting) 
            cout << -1 << " ";
         else  // if (v != starting) 
            cout << dist[v] << " ";
        
        std::cout << "\n";
    


Distances dijkstra(AdjList const& graph, Vertex source) 
    std::set<OutEdge> Q;

    vector<bool> visited(graph.size(), false);
    Distances    dist(graph.size(), INFINITY);  // Unkown distance from source
    Predecessors prev(graph.size(), UNDEFINED); // Previous node in optimal path from source

    dist[source] = 0; // Distance from source to source

    for (Vertex v = 1; v < graph.size(); v++)
        Q.insert(v, dist[v]);

    while (!Q.empty()) 
        Vertex u = Q.begin()->node;
        visited[u] = true;
        Q.erase(Q.begin());

        for (auto& v : graph[u])  // for each neighbour
            if (visited[v.node]) // where v is still in Q.
                continue;

            Weight alt = dist[u] + v.weight;

            if (alt < dist[v.node]) // A short path to v has been found
            
                dist[v.node] = alt;
                prev[v.node] = u;

                // update prio
                auto it = std::find_if(Q.begin(), Q.end(), [&](auto& oe)  return oe.node == v.node; );
                if (it != Q.end()) 
                    Q.erase(it);
                    Q.insert(v.node, alt);
                
            
        
    

    return dist;


int main() 
    size_t test;
    cin >> test;
    for (size_t i = 0; i < test; i++) 
        size_t nodes, edges;
        cin >> nodes >> edges;
        nodes += 1; // hack to avoid converting ids to base-0

        AdjList adj(nodes);
        for (size_t j = 0; j < edges; j++) 
            Vertex src, des;
            Weight weight;

            cin >> src >> des >> weight;
            assert(weight>=0);

            adj[src].push_back(des, weight);
            adj[des].push_back(src, weight);
        

        print_graph(adj);

        Vertex starting;
        cin >> starting;
        auto d = dijkstra(adj, starting);
        printResults(d, starting);
    

将结果打印为:

1 -> 0: -1 
1 -> 1: 0 
1 -> 2: 5 
1 -> 3: 7 
1 -> 4: 9 
1 -> 5: -1 
1 -> 6: 2 

注意您在djkstra [原文如此] 中至少还有一个错误

for (int i = 0; i<number_of_vertices-1; i++)

-1 似乎是完全错误的(如果有的话,它“应该”是+1,尽管在所有代码中散布+1 是一种巨大的代码气味。看看我是如何解决这个问题的我的版本。

说明

std::set&lt;&gt; 为具有独特元素的容器建模。在这种情况下,权重是等价性质,因此,所有具有相同权重的元素都是等价的。一开始,队列最多包含 2 个元素:1 个起始顶点(距离 0)和 1 个其他顶点都以相同距离播种(INFINITY;或您的版本中的 max)。

这将使您的算法陷入困境,因为从未访问过许多节点(一旦队列为空,算法就会停止)。

修复是 5 个字母:s/set/multiset/:

Fixed Version

打印:

1 -> 0: -1 
1 -> 1: 0 
1 -> 2: 5 
1 -> 3: 7 
1 -> 4: 7 
1 -> 5: 5 
1 -> 6: 2 

【讨论】:

我明白了,现在。感谢您的努力。 @sparrow2 最重要的是,我希望你能从这里带走 (a) 编写富有表现力的代码(富有表现力的命名,故意打字) (b) 调试(你会发现 queue 只有 2 项)。哦,在我忘记之前,(c) 提出好的问题(完整,带有期望和实际结果) 你应该使用binary_search而不是find_if,因为set中的成员已经安排好了 @bigxiao 您不能使用binary_search,因为它返回布尔值,对于非随机访问迭代器来说不是最理想的,并且在与不匹配排序的比较器一起使用时只是未定义的行为容器元素。也许您正在考虑std::lower_boundstd::upper_boundstd::equal_range,但这仍然是次优的和 UB。相反,也许您正在考虑std::set::find(它确实在内部实现了二进制搜索)但是 不起作用的集合是按权重排序的,而不是按节点排序的。换句话说:你很困惑。 哦,我犯了一个大错误;【参考方案2】:

所以,这是你的图表:

你的程序的输出是Live On Coliru

#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/dijkstra_shortest_paths.hpp>
#include <boost/graph/graph_utility.hpp>
#include <iostream>

using Graph = boost::adjacency_list<
        boost::vecS, boost::vecS, boost::directedS,
        boost::no_property,
        boost::property<boost::edge_weight_t, int>
    >;

int main() 
    std::cin.exceptions(std::ios::failbit);
    int test; std::cin >> test;
    for (int i = 0; i < test; i++) try 
        int nodes, edges;
        std::cin >> nodes >> edges;
        std::cout << "Nodes:" << nodes << " Edges:" << edges << "\n";

        Graph g(nodes + 1);

        for (int j = 0; j < edges; j++) 
            int src, des, weight;
            std::cin >> src >> des >> weight;
            std::cout << "src:" << src << " des:" << des << " weight:" << weight << "\n";

            add_edge(src, des, weight, g);
        

        boost::print_graph(g);

        int starting;
        std::cin >> starting;

        std::vector<int> dist(num_vertices(g));
        auto id = get(boost::vertex_index, g);

        dijkstra_shortest_paths(g, starting, 
                weight_map(get(boost::edge_weight, g))
                .distance_map(make_iterator_property_map(dist.begin(), id))
            );

        std::cout << "Distances:\n";
        for (Graph::vertex_descriptor v = 0; v < num_vertices(g); v++) 
            std::cout << starting << " -> " << v << " = " << dist[v] << "\n";
        
     catch(std::exception const& e) 
        std::cout << "Error: " << e.what() << "\n";
    

打印:

Nodes:6 Edges:7
src:1 des:2 weight:5
src:2 des:3 weight:2
src:3 des:4 weight:3
src:1 des:4 weight:9
src:4 des:5 weight:2
src:5 des:6 weight:3
src:1 des:6 weight:2
0 --> 
1 --> 2 4 6 
2 --> 3 
3 --> 4 
4 --> 5 
5 --> 6 
6 --> 
Distances:
1 -> 0 = 2147483647
1 -> 1 = 0
1 -> 2 = 5
1 -> 3 = 7
1 -> 4 = 9
1 -> 5 = 11
1 -> 6 = 2

没错!

这是算法的输出,动画通过所有可到达的目标顶点:

正确的距离其实是不言而喻的:

1: 0  
2: 5  
3: 5+2 = 7  
4: 9 (5+2+3 is larger)  
5: 9+2 = 11  
6: 2 (both 5+2+3+2+3 and 9+2+3 are way larger)

无方向更新

是的,如果它是无向的,the outcome is different:

【讨论】:

这里是用于生成 graphviz 可视化的相同代码的一个小变体:coliru.stacked-crooked.com/a/2e225d10dd44d54b(使用 these commands 创建动画) 如果图是有向的,这是正确的。在我们的例子中,它不是 字面意思是s/directedS/undirectedS/。这让我想知道您是否真的阅读过代码。以及为什么您没有在问题中包含任何预期结果。 Enhanced gif在答案中

以上是关于为啥这个 Dijkstra 算法不适用于这个特定的输入?的主要内容,如果未能解决你的问题,请参考以下文章

java Codingbat notAlone——为啥它不适用于这个特定的例子

不确定为啥这个特定的测试用例不适用于合并二叉树

Dijkstra 算法的负边缘

为啥 RegisterMessageHandler 不适用于特定主题名称?

Pygame:这个 spritesheet 代码适用于另一个项目,但不适用于这个项目。为啥?

为啥这个 CSS 不适用于 Android 上的 Chrome,但适用于其他任何地方?