C++ 使这个 Djikstra 实现更快

C++ Making this Djikstra implementation faster

本文关键字:实现 Djikstra C++      更新时间:2023-10-16

我正在为一个大图(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;
};

如果你的图形从未改变,解决方案是将其切成更小的图形。

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

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

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

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

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

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

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

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

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

您应该使用(常量)引用之一:

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

或指针:

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

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

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