【问题标题】:Implementing Dijkstra with adjacency list C++使用邻接表 C++ 实现 Dijkstra
【发布时间】:2018-07-08 22:38:15
【问题描述】:

我正在尝试为 Dijkstra 的算法编写用户定义对象的邻接列表。

这个项目是我工作的一部分,所以我必须保持一般性。

我正在使用邻接列表和 STL 优先级队列

这是我比较不同节点的方式

class priorityQueueCompareNodes
{
    public:
    bool operator() ( const Node& leftSideNode, 
                  const Node& rightSideNode  )
    {
        return leftSideNode.cost > rightSideNode.cost;
    }
};

这就是我弹出并插入优先级队列的方式。

//get the node with the lowest cost from the priority queue
Node closestNode = priorityQueue.top();

//pop this node from the queue
priorityQueue.pop();

当我放松边并将成本更改为不同节点时,我会更改邻接列表,而不是优先级队列。

我的问题是如何更改优先级队列中的值,以便每次放松边权重和更改距离/父级时都有更新的优先级队列。

谢谢大家。

【问题讨论】:

  • 这个项目是我工作的一部分 -- 请注意boost 已经实现了 Dijkstra 的算法。
  • 如何更改优先级队列中的值,以便每次放松边权重和更改距离/父级时都有更新的优先级队列。 -- 换句话说, “每次更改 时如何更改 中的值”。这就是你的问题吗?如果是这样,观察者模式,使用std::shared_ptr,根据您的要求,有多种方法可以给猫剥皮。
  • 一种方法是使用 std::set,删除元素并使用新键重新插入。这在 O(log n) 中运行。
  • 我不知道这是否是一个好习惯,但我所做的只是当我找到一个较短的节点路径时将一个新元素插入优先级队列。新条目将首先得到处理,路径较长的旧条目将被忽略,因为该节点已经被访问过。
  • @FeiXiang 可以做到这一点,但是根据可能正在处理的特定图表类型,优先级队列的空间使用量可能会变得非常大。

标签: c++ algorithm


【解决方案1】:

使用任何类型的指针来访问std::priority_queue 的内部工作以更新其中的元素是有问题的。例如,在最小优先级队列中,数据结构保证节点的值小于其子节点的值。如果您选择更新优先级队列中的值,则必须手动执行冒泡操作以保持该保证的完整性。手动进行气泡向上/向下操作有点比使用 std::priority_queue 之类的集合要好。

使用std::set 提供了一个可行的替代方案。要检索最小值和最大值,您可以简单地使用集合的beginrbegin 指针。除了集合之外,您应该为节点保留一个最小距离M 的数组/向量(即随机访问集合)。每当您的松弛产生到节点v 的更短路径时,您可以删除v 的旧集合条目,更新M[v],并将v 的条目插入到具有新M[v] 值的集合中。更新M[v] 是在恒定时间内完成的,设置操作需要O(log|V|) 时间。因此,即使它具有比优先级队列更大的常数因子,使用集合也具有相同的渐近复杂度。

以下是说明用法的伪代码。由于您的代码不完整,我求助于使用标准数据类型。图的顶点是 0 索引的,对于 vi,邻接表 adjListadjList[i] 中保留一个出边向量。对于每条边,分别保留目的顶点的索引和该边的权重。

void dijkstra(vector< vector< pair<int, long long> > >& adjList, int source) {
    set< pair<long long, int> > priorityQueue;
    vector< long long > minCosts(adjList.size(), INFINITY);
    minCosts[source] = 0LL;
    priorityQueue.insert( make_pair(minCosts[source], source) );

    while(!priorityQueue.empty()) {
        set< pair<long long, int> >::iterator it = priorityQueue.begin();
        int u = it->second;
        long long costU = it->first;

        for(int i=0; i < adjList[u].size(); ++i) {
            int v = adjList[u][i].first;
            long long w = adjList[u][i].second;
            if (costU + w < minCosts[v]) {
                if(minCosts[v] < INFINITY) {
                    priorityQueue.erase( make_pair(minCosts[v], v) );
                }
                minCosts[v] = costU + w;
                priorityQueue.insert( make_pair(minCosts[v], v) );
            }
        }
    }

    // minCosts[] now keeps the minimal distance of all nodes from source
}

显然,在您的代码库中,边缘成本和相关邻接列表条目的检索可能会有所不同,但由于您没有提供这些详细信息,因此我尝试提供一个更通用的代码示例,以最小化强调这一点。基本上,该集合可能只保留一对成本和节点索引,并且您已经设置好了。

或者,您可以为vehicleNode 类型定义一个比较运算符并将其保留在集合中,而不是一对成本和节点。但这只是一个特定于实现的决定,而不是您问题的重点。

【讨论】:

  • 问题是在 Dijkstra 的运行过程中,两个节点与源的距离可能相同。这可以防止使用std::set。另一方面,使用multi_set 会给您带来麻烦。如果某个时间节点v 和节点u 与源的距离相同,那么delete v 确实可以从multi_set 中删除u....
  • 与源节点距离相等的两个节点不妨碍使用集合。让我用一个最小的代码示例来澄清我的答案。
  • @fjardon 我添加了一个代码示例,说明std::set 的示例用法以模拟优先队列功能。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-04-10
  • 1970-01-01
  • 2012-12-09
  • 1970-01-01
  • 1970-01-01
  • 2014-04-01
  • 1970-01-01
相关资源
最近更新 更多