std::priority_queue 与 std::set 的 Dijkstra 最短路径算法性能

Mer*_*eur 5 c++ algorithm performance stl graph

我想了解这些容器在时间复杂度方面的主要区别。我尝试了 Dijkstra 算法的 3 种实现,如下所述:

1-使用一个简单的数组作为队列

2-使用STLpriority_queue

3-带有STL集

我测试过的图相当大,它包含超过 150000 个顶点,有方向且所有边的权重均为正。

我得到的结果如下:

1 - 使用数组时,算法相当慢 --> 这是预期的

2 - 使用 STLpriority_queue 算法运行速度比数组快很多 --> 这也是预期的

3 - 使用STL设置,算法运行得非常快,我说的是比priority_queue快100倍 --> 我没想到会看到如此巨大的性能......

知道 std::priority_queue 和 std::set 是存储元素的数据容器,并且两者都具有基本相同的插入复杂度 O(log n),我不明白它们之间的性能差异如此之大。对此你有什么解释吗?

感谢您的帮助,

编辑: 这是我的实现的摘要:

与 std::set:

unsigned int Graphe::dijkstra(size_t p_source, size_t p_destination) const {

....


set<pair<int, size_t>> set_vertices;


vector<unsigned int> distance(listAdj.size(),
        numeric_limits<unsigned int>::max());


vector < size_t
        > predecessor(listAdj.size(),
                numeric_limits < size_t > ::max());


distance[p_source] = 0;
set_vertices.insert( { 0, p_source });


while (!set_vertices.empty()) {

    unsigned int u = set_vertices.begin()->second;


    if (u == p_destination) {
        break;
    }

    set_vertices.erase( { distance[u],
            u });


    for (auto itr = listAdj[u].begin();
            itr != listAdj[u].end(); ++itr) {


        int v = itr->destination;
        int weigth = itr->weigth;


        if (distance[v]
                > distance[u] + weigth) {


            if (distance[v]
                    != numeric_limits<unsigned int>::max()) {
                set_vertices.erase(
                        set_vertices.find(
                                make_pair(distance[v],
                                        v)));
            }

            distance[v] = distance[u] + weigth;

            set_vertices.insert( { distance[v],
                    v });

            predecessor[v] = u;
        }
    }
}

....

return distance[p_destination];}
Run Code Online (Sandbox Code Playgroud)

和priority_queue:

unsigned int Graphe::dijkstra(size_t p_source, size_t p_destination) const {

...

typedef pair<size_t, int> newpair;

priority_queue<newpair, vector<newpair>, greater<newpair> > PQ;

vector<unsigned int> distance(listAdj.size(),
        numeric_limits<unsigned int>::max());


vector < size_t
        > predecessor(listAdj.size(),
                numeric_limits < size_t > ::max());


distance[p_source] = 0;
PQ.push(make_pair(p_source, 0));

while (!PQ.empty()) {


    unsigned int u = PQ.top().first;


    if (u == p_destination) {
        break;
    }

    PQ.pop();


    for (auto itr = listAdj[u].begin();
            itr != listAdj[u].end(); ++itr) {


        int v = itr->destination;
        int weigth = itr->weigth;


        if (distance[v]
                > distance[u] + weigth) {

            distance[v] = distance[u] + weigth;

            PQ.push(
                    make_pair(v, distance[v]));

            predecessor[v] = u;
        }
    }
}
...

return distance[p_destination];}
Run Code Online (Sandbox Code Playgroud)

Kai*_*dul -2

其底层数据结构std::priority_queue是最大堆及其std::set自平衡二分搜索——基本上是 C++ 的红黑树。所以两者都保证了O(logn)插入、删除和更新操作的时间复杂度。

但是,正如我提到的,平衡二叉搜索树std::set会自动平衡,以保持其节点数量的对数高度,这确保了对数查询复杂性,无论插入顺序或任何操作之后如何。std::priority_queue不是自平衡的,并且根据插入顺序可能非常平坦。尽管自平衡有其自身的成本,并且在移除顶部后的 heapify 中也是如此,但我认为这就是性能增益的原因。

希望能帮助到你!

  • 堆是一个完全二叉树,它不可能“非常平坦,具体取决于插入顺序” (4认同)