C++ 使这个 Djikstra 实现更快

Posted

技术标签:

【中文标题】C++ 使这个 Djikstra 实现更快【英文标题】:C++ Making this Djikstra implementation faster 【发布时间】:2018-04-15 07:10:06 【问题描述】:

我正在为一个大图(40k 节点,100k 弧)实现 Djikstra 算法。对于较短的路径,对于较大的路径(从一端到另一端),搜索时间不到一秒,这需要几分钟才能完成。我也在搜索后绘制路径,所以我使用了一些 Qt 对象。我怎样才能让它更快?由于地图结构,我在搜索邻居时感觉我在浪费时间。

这是班级

class PathFinder 
public:
   void findPath2(const Node & start, Node & finish);

   static PathFinder& getInstance();
   PathFinder();
   PathFinder(Gps gps);
   ~PathFinder();

   unsigned int* getPrev() const;
   void setPrev(unsigned int* prev);
   QVector<unsigned int> makePath(int target);

   GraphicNode* getTo();
   GraphicNode* getFrom();

   void setTo(GraphicNode* node);
   void setFrom(GraphicNode* node);

   class Compare
   
   public:
      bool operator() (std::pair<Node*, int> a, std::pair<Node*, int> b)
      
         return a.second > b.second;
      
   ;


private:
   static PathFinder* _pathfinder;
   Gps _gps;
   GraphicNode* _from;
   GraphicNode* _to;

   unsigned int* _prev;
   unsigned int* _dist;
   unsigned int _notVisited;

   bool selectedNode = false;


   Node* getMinNode();
   bool hasNotVisited();
;

这是搜索功能

void PathFinder::findPath2(const Node& start, Node& finish)

   QVector<Node> nodes=_gps.graph().nodes();

   std::priority_queue<std::pair<Node*,int>,std::vector<std::pair<Node*, int>>,Compare> q;

   _dist[start.id()] = 0;
   for (int i = 0; i < nodes.size(); i++) 
      std::pair<Node*, int> p = std::make_pair(const_cast<Node*>(&nodes.at(i)), _dist[i]);
      q.push(p);
   

   while (!q.empty()) 
      std::pair<Node*, int> top = q.top();
      q.pop();
      Node* minNode = top.first;
      QMap<Node*, unsigned short> nextNodes = minNode->nextNodes();

      if (*minNode == finish) 
         return;
      
      int minNodeId = minNode->id();
      for (QMap<Node*, unsigned short>::iterator iterator=nextNodes.begin(); iterator != nextNodes.end(); iterator++) 
         Node* nextNode = iterator.key();
         int altDist = _dist[minNodeId] + nextNodes.value(nextNode);
         int nextNodeId = nextNode->id();
         if (altDist < _dist[nextNodeId]) 
            _dist[nextNodeId] = altDist;
            _prev[nextNodeId] = minNodeId;
            std::pair<Node*, int> p = std::make_pair(nextNode, _dist[nextNodeId]);
            q.push(p);
         
      
   

这是节点的结构,它包含一个到其邻居的映射,以权重为值,x和y是稍后绘制它的坐标,不要介意

class Node 
private:
   unsigned short _id;
   double _y;
   double _x;
   QMap<Node*, unsigned short> _nextNodes;
   bool _visited = false;


public:
   Node();
   Node(unsigned short id, double longitude, double latitude);

   unsigned short id() const;
   double y() const;
   void setY(double y);
   double x() const;
   void setX(double x);

   bool operator==(const Node& other);
   void addNextNode(Node* node, unsigned short length);

   QMap<Node*, unsigned short> nextNodes() const;
;

【问题讨论】:

我投票结束这个问题,因为它属于Code Review。 你考虑过使用A*吗? const QMap&lt;Node*, unsigned short&gt; &amp; nextNodesconst QMap&lt;Node*, unsigned short&gt;&amp; nextNodes() const 开始,看看它能让你走多远。 【参考方案1】:

如果您的图表永远不会改变,解决方案是将其切割成更小的图表。

    计算每个小图的边之间的最短距离并存储结果(edge1、edge2、距离、内部最短路径)。 计算 2 点之间的距离时,请在到达“小图”边时使用存储的结果。

我相信这是 GoogleMaps 和其他路径查找器工作的那种技巧。

缺点是初始成本(如果您的图表真的永远不会改变,您可以将这些“迷你”最短路径一劳永逸地存储在文件或数据库中)。您必须仔细选择小图的大小,因为访问存储的结果也会有(时间)成本(无论是在大型内存映射还是数据库中)。必须找到一个平衡点。

如果经常返回相同的路径搜索,另一个想法是存储搜索次数最多的结果。

【讨论】:

【参考方案2】:

如果您使用优先级队列和邻接列表,则您的实现复杂度为O((E + V) log V)。这应该足以在任何体面的 CPU 上在您的图表上在几毫秒内计算出任何最短路径。

您似乎正确完成了优先队列部分。但是为什么要使用地图而不是邻接表呢?这似乎有点矫枉过正。

您的实现隐藏了一些额外的、不必要的工作:

QMap<Node*, unsigned short> nextNodes = minNode->nextNodes();

这将创建 nextNodes 返回的任何内容的副本。这意味着对于每个节点,您将复制其所有连接的节点,这已经是O(V^2)。您可能认为您的QMap 包含指向Node* 的指针,因此没有进行复制。但是您将复制k 指针,每个相邻节点一个,这仍然很糟糕。

您应该使用 (const) 引用:

const QMap<Node*, unsigned short>& nextNodes = minNode->nextNodes();

或者指针:

QMap<Node*, unsigned short>* nextNodes = minNode->nextNodes();

仅此一项就应该有很大帮助。之后,我会切换到链表。 QMap 是使用红黑树实现的,因此迭代它会比迭代链表慢。

如果您的图表增长得更多,user6106573 的建议非常好(但对于您当前的图表大小来说完全是矫枉过正)。另一个建议可能是选择不完全 the 最短的最短路径:https://en.wikipedia.org/wiki/A*_search_algorithm - 检查Bounded relaxation 部分。同样,对于您当前的图形大小,这不是必需的。

【讨论】:

感谢您的提示,我确实将其更改为对列表而不是 QMap,并且我现在使用指向它的指针,但时间仍然相同,所以我想某处还有另一个问题 std::list >* Node::nextNodes() const return _nextNodes;

以上是关于C++ 使这个 Djikstra 实现更快的主要内容,如果未能解决你的问题,请参考以下文章

Java实现Djikstra双栈算式解释器, 识别添加优先级

对于 C++ 中的图问题,邻接列表或邻接矩阵哪个更好?

如何使具有多个联合的 SQL 更快以实现高速

在 C++ 中通过 csv 文件的更快方法

如何在 C++ 中使 csv 文件运行得更快

新手用C++写了个泛型堆,效率竟比STL的更快?