【问题标题】:Why does my implementation of Dijkstra's algorithm not behave as it should?为什么我对 Dijkstra 算法的实现没有按应有的方式运行?
【发布时间】:2021-07-18 15:25:45
【问题描述】:

我正在编写 Dijkstra 算法的实现,以了解酷图算法(这不是家庭作业,仅供参考)。我使用 Wikipedia 的 description 算法作为我的主要资源。

我测试了不同的遍历路径,得到以下结果((foo, bar) 表示foo to bar):

crashes:
    (e, f)
    (f, d)
    (c, a)
    (f, g)
incorrect:
    (a, c)
    (g, f)
working:
    (d, f)

我正在使用的图表如下所示:

       F - H
       |   |
A ---- B - C
|         /
|        /
E - G - D

通过跟踪从EF 的路径,我大致了解了我的代码失败的原因。另一个问题是我不知道如何使用我的方式来实现算法,否则。这是从EF 的跟踪:

在节点E,我的邻居是AG。最短的暂定距离是G,所以这是下一个当前节点。 G的邻居是ED,但是E已经被遍历过了,所以C是下一个。对于C,它的邻居D 被遍历,所以我们现在到达BBH 是等距的,但它在C 的边列表中被首先选择)。这是我的问题所在:

A 的暂定距离已由 E 计算为 2。由于从 BA 的新暂定距离远大于两个,因此它的距离保持在 2。对于F,它的距离设置为暂定距离,因为它被初始化为infinityA的距离比较小,所以选择它作为下一个节点。 A的唯一邻居是EB,它们已经被遍历过了,所以它周围的所有节点都已经被探索过了。变量closest(请参阅下面的代码)被初始化为一个节点,除了distancedistance 之外,没有其他填充字段@ 987654358@,因此对于下一次迭代,它没有边,并且我得到了分段错误.

我知道这是我的代码中发生的事情,因为它的输出如下所示:

Current: e
New neighbor: a
New neighbor: g
g, closest, distance of 1
Current: g
New neighbor: d
d, closest, distance of 2
Current: d
New neighbor: c
c, closest, distance of 4
Current: c
New neighbor: b
New neighbor: h
b, closest, distance of 5
Current: b
New neighbor: a
New neighbor: f
a, closest, distance of 2
Current: a
?, closest, distance of 1000
Current: ?
Segmentation fault: 11

我在实现这个算法时哪里出错了?我试图非常仔细地遵循维基百科对它的 6 步描述。他们的描述和我的唯一区别是我没有使用集合来跟踪已探索和未探索的节点(相反,数据保存在节点本身中)。请提供您可以提供的任何见解。

注意:我在没有优化的 Mac 上使用 Clang 进行编译 (-O0)。我注意到,通过更高的优化,我的程序会无限重复,然后给我另一个分段错误,但我会优先解决我的算法的核心问题,然后再处理它。

#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>

#define infinity 1000

struct Node {
    unsigned char value;
    int visited, distance, edge_count;
    int* weights, weight_assign_index, freed;
    struct Node** edges;
};

typedef struct Node Node;

Node* init_node(const unsigned char value, const int edge_count) {
    Node* node = malloc(sizeof(Node));

    node -> value = value;
    node -> visited = 0;
    node -> distance = infinity;
    node -> edge_count = edge_count;

    node -> weights = malloc(edge_count * sizeof(int));
    node -> weight_assign_index = 0;
    node -> freed = 0;

    node -> edges = malloc(edge_count * sizeof(Node*));

    return node;
}

void assign_edges(Node* node, const int amount, ...) {
    va_list edges;
    va_start(edges, amount);

    for (int i = 0; i < amount; i++)
        node -> edges[i] = va_arg(edges, Node*);

    va_end(edges);
}

void assign_weight(Node* node_1, Node* node_2, const int weight) {
    for (int i = 0; i < node_1 -> edge_count; i++) {
        if (node_1 -> edges[i] == node_2) {
            node_1 -> weights[node_1 -> weight_assign_index++] = weight;
            node_2 -> weights[node_2 -> weight_assign_index++] = weight;
        }
    }
}

void deinit_graph(Node* node) {
    if (!node -> freed) {
        node -> freed = 1;
        free(node -> weights);
        for (int i = 0; i < node -> edge_count; i++)
            deinit_graph(node -> edges[i]);
        free(node -> edges);
    }
}

void dijkstra(Node* current, Node* goal) {
    Node local_closest;
    local_closest.distance = infinity;
    Node* closest = &local_closest;

    printf("Current: %c\n", current -> value);

    for (int i = 0; i < current -> edge_count; i++) {
        Node* neighbor = current -> edges[i];
        if (!neighbor -> visited) {
            printf("New neighbor: %c\n", neighbor -> value);
            const int tentative_distance = current -> distance + current -> weights[i];

            if (tentative_distance < neighbor -> distance)
                neighbor -> distance = tentative_distance;

            if (neighbor -> distance < closest -> distance)
                closest = neighbor;
        }
    }
    printf("%c, closest, distance of %d\n", closest -> value, closest -> distance);

    current -> visited = 1;
    if (closest == goal) printf("Shortest distance is %d\n", closest -> distance);
    else dijkstra(closest, goal);
}

int main() {
    Node
        *a = init_node('a', 2),
        *b = init_node('b', 3),
        *c = init_node('c', 3),
        *d = init_node('d', 2),
        *e = init_node('e', 2),
        *f = init_node('f', 2),
        *g = init_node('g', 2),
        *h = init_node('h', 2);

    assign_edges(a, 2, e, b);
    assign_edges(b, 3, a, f, c);
    assign_edges(c, 3, b, h, d);
    assign_edges(d, 2, c, g);
    assign_edges(e, 2, a, g);
    assign_edges(f, 2, b, h);
    assign_edges(g, 2, e, d);
    assign_edges(h, 2, f, c);

    assign_weight(a, e, 2);
    assign_weight(a, b, 4);
    assign_weight(b, c, 1);
    assign_weight(b, f, 1);
    assign_weight(f, h, 1);
    assign_weight(h, c, 1);
    assign_weight(c, d, 2);
    assign_weight(d, g, 1);
    assign_weight(g, e, 1);


    e -> distance = 0;
    dijkstra(e, f);
    deinit_graph(a);
}

【问题讨论】:

  • 考虑:如果没有有效的最近节点,是否应该递归?在这种情况下,当你递归时会发生什么?
  • @1201ProgramAlarm 当然;如果您没有有效的最近节点,则重复出现是没有意义的。如果我说if (closest -&gt; distance == infinity) return;,我的实现在找到解决方案之前就停止了,这是不正确的。我的假设是否正确,总会有一个有效的最近节点?我还没有找到一个例子来说明如何处理没有节点有效的情况。

标签: c graph shortest-path dijkstra graph-traversal


【解决方案1】:

再次阅读维基百科算法的第 6 步:

否则,选择标记为最小暂定距离的未访问节点,将其设置为新的“当前节点”,并返回步骤3。

这里的“选择”是指“在整个图中所有未访问的节点中”,而不仅仅是在当前节点的未访问邻居中,这就是您的代码正在做的事情。因此,如果最小暂定距离的未访问节点不是当前节点的邻居,您的代码就会误入歧途。如果当前节点根本没有未访问的邻居(这完全有可能,无论是您遇到的情况,还是更简单的死路节点),您的代码会荒谬地访问 local_closest 节点,这不是t 在图中,并且其边未初始化,自然会导致崩溃。

因此,您在访问您关注的 A 之前就偏离了正确的算法。当您完成访问 D 时,其余未访问的节点是暂定距离 2 处的 A,暂定距离 4 处的 C,以及暂定距离无穷远处的 B、F、H。所以按照算法,你应该接下来访问A。但是您再次访问 C,因为您的代码错误地将当前节点的邻居视为下一个要访问的节点的候选者。

坦率地说,我根本看不到递归算法在这里是如何可行的。您需要访问一些跟踪所有未访问节点的数据结构,以便在任何时候您都可以在最小暂定距离处找到一个,即使它离您当前的节点很远。您在节点本身上跟踪访问状态的想法对此存在问题,因为您没有好的方法来搜索它们,除非返回到起始节点并执行某种 DFS/BFS。这 (1) 在您当前的实现中是不可能的,因为对 dijkstra 的递归调用不再具有指向起始节点的指针,并且 (2) 效率非常低,因为每次访问都是 O(N)。

维基百科算法建议在此处使用集合是有充分理由的。而且我认为它更适合迭代而不是递归算法。

【讨论】:

  • 这很有意义。起初我想知道为什么没有像我这样的解决方案,但这清除了它。我现在尝试使用 2 套。
猜你喜欢
  • 2013-05-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-01-01
  • 2015-02-27
  • 1970-01-01
  • 2021-12-19
相关资源
最近更新 更多