提问者:小点点

了解Dijkstra优先级队列与集合实现的时间复杂度


考虑dijkstra算法的以下两个实现:

使用set:

// V - total number of vertices
// S - source node
void dijkstra(int V, vector<vector<int>> edge[], int S)
{
    vector<int> dist(V, 1e9);
    dist[S] = 0;
    
    set<pair<int, int>> s;
    for (int  i=0; i<V; i++)
        s.insert({dist[i], i});    // log(V) time
    
    while (!s.empty())    // exactly V times
    {
        auto top = *(s.begin());    // constant time
        int dis = top.first;
        int node = top.second;
        s.erase(top);               // amortised constant time

        for (auto it: edge[node])    // For all the iterations of outer while loop this will sum up to E, where E is the total number of edges in the graph
        {
            int nb = it[0];
            int edge_weight = it[1];
            
            if (dist[nb] > dis + edge_weight)
            {
                s.erase({dist[nb], nb});    // log(V) time
                dist[nb] = dis + edge_weight;
                s.insert({dist[nb], nb});    // log(V) time
            }
        }
    }
}

使用优先级队列:

// V - total number of vertices
// S - source node
void dijkstra(int V, vector<vector<int>> edge[], int S)
{
    vector<int> dist(V, 1e9);
    dist[S] = 0;
    
    priority_queue<pair<int, int> , vector<pair<int, int>>, greater<pair<int, int>>> pq;
    pq.push({dist[S], S});
    
    while (!pq.empty())    // Can be more than V times, let's call this heap_size times
    {
        int node = pq.top().second;    // O(1) time
        pq.pop();                      // log(heap_size) time
        
        for (int i=0; i<edge[node].size(); i++)    // Can be approximated to (E/V), where E is the total number of edges in the graph
        {
            int nb = edge[node][i][0];
            int edge_weight = edge[node][i][1];

            if (dist[nb] > dist[node] + edge_weight) {
                dist[nb] = dist[node] + edge_weight;
                pq.push({dist[nb], nb});    // log(heap_size) time
            }
        }
    }
    return dist;
}

使用基于集合的方法找到时间复杂度很容易,因为集合中元素的数量正好是V(顶点数),并且每个边都运行内部for循环,因此它的时间复杂度是O(V*log(V)V E*log(V)),相当于O(E*log(V))(原因:Dijkstra算法的时间复杂度是多少)

但是我很难理解priority_queue方法的时间复杂度。这里同一个节点可以在不同距离的priority_queue中多次出现。如何计算添加到堆中的节点数量的上限?

我还想根据图的性质(稀疏vs密集)来决定使用哪个实现,这两种实现对于任何类型的图都是等价的吗?


共1个答案

匿名用户

您的priority_queue版本不太正确。

您的while循环应该像这样开始:

    auto nextPair = pq.top();
    pq.pop();
    if (dist[nextPair.second] != nextPair.first) {
        continue;
    }
    int node = nextPair.second;

这确保了当其当前记录从优先级队列中弹出时,每个顶点只被处理一次。

然后复杂性分析变得容易,因为每个边最多处理一次,然后最多有|E|插入优先级队列。

总复杂度为O(E log E),并且由于E

优先级队列方法的主要缺点是它会消耗O(E)空间。这通常没问题,因为它与图本身消耗的空间相当。由于priority_queueset快得多,因此priority_queue版本是实践中常见的方式。