@Iyatsu

Are you sure you want to delete the question?

If your question is resolved, you may close it.

Leaving a resolved question undeleted may help others!

We hope you find it useful!

第K最短路のプログラム作成

Q&A

解決したいこと

与えられた始点と終点に対して、1<=k<=5番目までの短い経路の距離を求めたい

発生している問題・エラー

k=1(最短経路)の距離を求めるプログラムは完成しているが、k>=2以降を求める方法が分からない

該当するソースコード

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>

#define MAXN 1100     // 最大ノード数(地点+交差点)
#define MAXM 500      // 最大セグメント数(線分の数)
#define MAXQ 100      // 最大クエリ数
#define INF 1e18      // 無限大の代用
#define EPS 1e-8      // 誤差の許容範囲

// 座標を表す構造体
typedef struct {
    double x, y;
} Point;

// 線分を表す構造体(2点のインデックス)
typedef struct {
    int a, b;
} Segment;

// グラフのエッジを表す構造体(隣接リスト形式)
typedef struct Edge {
    int to;              // 接続先ノード
    double cost;         // コスト(距離)
    struct Edge* next;   // 次のエッジ
} Edge;

Point points[MAXN];      // 地点と交差点を格納
Segment segs[MAXM];      // 線分情報を格納
Edge* graph[MAXN];       // グラフの隣接リスト
int node_count = 0;      // 現在のノード数

// 2点が同一かどうかの判定(誤差許容)
int equal(Point p1, Point p2) {
    return fabs(p1.x - p2.x) < EPS && fabs(p1.y - p2.y) < EPS;
}

// 2点間のユークリッド距離を計算
double distance(Point p1, Point p2) {
    double dx = p1.x - p2.x, dy = p1.y - p2.y;
    return sqrt(dx * dx + dy * dy);
}

// 2線分が交差するかを判定し、交点の座標を返す
int intersect(Point p1, Point q1, Point p2, Point q2, double* ix, double* iy) {
    double a = q1.x - p1.x;
    double b = -(q2.x - p2.x);
    double c = q1.y - p1.y;
    double d = -(q2.y - p2.y);
    double e = p2.x - p1.x;
    double f = p2.y - p1.y;
    double det = a * d - b * c;
    if (fabs(det) < EPS) return 0; // 平行
    double s = (d * e - b * f) / det;
    double t = (-c * e + a * f) / det;
    // 線分の範囲内で交差していなければ無効
    if (s < EPS || s > 1 - EPS || t < EPS || t > 1 - EPS) return 0;
    *ix = p1.x + s * (q1.x - p1.x);
    *iy = p1.y + s * (q1.y - p1.y);
    // 端点同士の交差を除外
    if (equal((Point){*ix, *iy}, p1) || equal((Point){*ix, *iy}, q1) ||
        equal((Point){*ix, *iy}, p2) || equal((Point){*ix, *iy}, q2))
        return 0;
    return 1;
}

// グラフにエッジを追加(双方向)
void add_edge(int u, int v, double cost) {
    Edge* e = malloc(sizeof(Edge));
    e->to = v;
    e->cost = cost;
    e->next = graph[u];
    graph[u] = e;
}

// 座標を昇順ソート(x → y)
int cmp_point(const void* a, const void* b) {
    Point* p = (Point*)a;
    Point* q = (Point*)b;
    if (fabs(p->x - q->x) > EPS) return p->x < q->x ? -1 : 1;
    if (fabs(p->y - q->y) > EPS) return p->y < q->y ? -1 : 1;
    return 0;
}

// ノードのインデックスを取得(新規点は追加)
int get_node_index(Point p) {
    for (int i = 0; i < node_count; i++) {
        if (equal(points[i], p)) return i;
    }
    points[node_count++] = p;
    return node_count - 1;
}

// グラフ構築:交点の検出と分割、エッジ追加
void build_graph(int M) {
    for (int i = 0; i < M; i++) {
        Point p1 = points[segs[i].a];
        Point p2 = points[segs[i].b];
        Point seg_pts[MAXN];
        int count = 0;
        seg_pts[count++] = p1;
        seg_pts[count++] = p2;

        // 他の線分との交点を調査
        for (int j = 0; j < M; j++) {
            if (i == j) continue;
            double ix, iy;
            if (intersect(p1, p2, points[segs[j].a], points[segs[j].b], &ix, &iy)) {
                seg_pts[count++] = (Point){ix, iy};
            }
        }

        // 分割点を並べてソート
        qsort(seg_pts, count, sizeof(Point), cmp_point);

        // 順にエッジを張る
        for (int k = 0; k < count - 1; k++) {
            int u = get_node_index(seg_pts[k]);
            int v = get_node_index(seg_pts[k + 1]);
            double d = distance(seg_pts[k], seg_pts[k + 1]);
            add_edge(u, v, d);
            add_edge(v, u, d);
        }
    }
}

// ノードの識別子(地点 or 交差点)をパース
int parse_node(char* s, int N) {
    if (s[0] == 'C') {  // 交差点
        int id = atoi(s + 1);
        if (id < 1 || id > node_count - N) return -1;
        return N + id - 1;
    } else {  // 地点
        int id = atoi(s);
        if (id < 1 || id > N) return -1;
        return id - 1;
    }
}

// ダイクストラ法による最短経路計算
void dijkstra(int start, double dist[]) {
    int used[MAXN] = {0};
    for (int i = 0; i < MAXN; i++) dist[i] = INF;
    dist[start] = 0;
    for (int i = 0; i < node_count; i++) {
        int v = -1;
        for (int j = 0; j < node_count; j++) {
            if (!used[j] && (v == -1 || dist[j] < dist[v])) v = j;
        }
        if (dist[v] == INF) break;
        used[v] = 1;
        for (Edge* e = graph[v]; e; e = e->next) {
            if (dist[e->to] > dist[v] + e->cost) {
                dist[e->to] = dist[v] + e->cost;
            }
        }
    }
}

// メイン関数:入力処理とクエリ応答
int main() {
    int N, M, P, Q;
    scanf("%d %d %d %d", &N, &M, &P, &Q);  // 地点数・線分数・交差点数(未使用)・クエリ数

    // 地点の座標を読み込み
    for (int i = 0; i < N; i++) {
        scanf("%lf %lf", &points[i].x, &points[i].y);
    }
    node_count = N;

    // 線分の情報を読み込み
    for (int i = 0; i < M; i++) {
        scanf("%d %d", &segs[i].a, &segs[i].b);
        segs[i].a--; segs[i].b--;
    }

    // グラフ構築
    build_graph(M);

    // クエリ処理
    for (int q = 0; q < Q; q++) {
        char s1[10], s2[10];
        int k; // 未使用
        scanf("%s %s %d", s1, s2, &k);
        int start = parse_node(s1, N);
        int goal = parse_node(s2, N);
        if (start == -1 || goal == -1) {
            printf("NA\n");
            continue;
        }
        double dist[MAXN];
        dijkstra(start, dist);
        if (dist[goal] == INF) printf("NA\n");
        else printf("%.5f\n", dist[goal]);  // 最短距離を出力
    }

    return 0;
}

自分で試したこと

最短経路の距離については、
6 5 0 5
0 0
2 5
4 7
5 5
7 1
9 5
1 4
1 6
2 5
3 5
4 6
1 4 1
5 6 1
C1 6 1
C1000 1 1
C1 C3 1
と入力したところ、
7.07107
6.10882
5.88562
NA
2.68432
という出力が確認できた。

0 likes

1Answer

ソースコードはコードブロックに正く書かないと、#includeの後ろとか表示されませんね。

書き方
```c
//ここにコードを挿入する
```

1Like

Comments

  1. @Iyatsu

    Questioner

    ご指摘ありがとうございます。

  2. アポストロフィー ' ではなく、バッククォート ` を3つです。
    (JISキーボードなら、shift+@

  3. @Iyatsu

    Questioner

    再度編集しましたがこれでどうでしょうか?

  4. 上のコードは、クエリで与えられた 2地点間の(k=1:ダイクストラ法で求めた最短)距離を出力しています。

    k>=2 なら、2地点間のたどり方を 変数graphをたどって(dfsで)全探索して、距離別にソートして、k番目を出力するのですかね?
    「同じ地点は多くても1度だけ通る」制約を設けないと、無限ループしそうです。

  5. @Iyatsu

    Questioner

    改めてコードを作成したら以下のようになりました。

    #include <stdio.h>
    #include <stdlib.h>
    #include <math.h>
    #include <string.h>
    
    #define MAXN 1100
    #define MAXM 500
    #define MAXQ 100
    #define MAX_K 5  // 最大K値
    #define INF 1e18
    #define EPS 1e-8
    
    typedef struct {
        double x, y;
    } Point;
    
    typedef struct {
        int a, b;
    } Segment;
    
    typedef struct Edge {
        int to;
        double cost;
        struct Edge* next;
    } Edge;
    
    typedef struct {
        int node;
        double dist;
    } HeapNode;
    
    typedef struct {
        HeapNode data[MAXN * MAX_K];
        int size;
    } PriorityQueue;
    
    Point points[MAXN];  // 地点 + 交差点
    Segment segs[MAXM];
    Edge* graph[MAXN];
    int node_count = 0;
    
    // K番目までの各ノードへの距離を保存
    double dist_k[MAXN][MAX_K];
    // 各ノードについて、何個の経路が見つかったか
    int path_count[MAXN];
    
    // 優先度付きキューの操作
    void heap_init(PriorityQueue* pq) {
        pq->size = 0;
    }
    
    void heap_push(PriorityQueue* pq, int node, double dist) {
        int i = pq->size++;
        while (i > 0) {
            int p = (i - 1) / 2;
            if (pq->data[p].dist <= dist) break;
            pq->data[i] = pq->data[p];
            i = p;
        }
        pq->data[i].node = node;
        pq->data[i].dist = dist;
    }
    
    HeapNode heap_pop(PriorityQueue* pq) {
        HeapNode res = pq->data[0];
        HeapNode x = pq->data[--pq->size];
        int i = 0;
        while (2 * i + 1 < pq->size) {
            int a = 2 * i + 1, b = 2 * i + 2;
            if (b < pq->size && pq->data[b].dist < pq->data[a].dist) a = b;
            if (pq->data[a].dist >= x.dist) break;
            pq->data[i] = pq->data[a];
            i = a;
        }
        pq->data[i] = x;
        return res;
    }
    
    int equal(Point p1, Point p2) {
        return fabs(p1.x - p2.x) < EPS && fabs(p1.y - p2.y) < EPS;
    }
    
    double distance(Point p1, Point p2) {
        double dx = p1.x - p2.x, dy = p1.y - p2.y;
        return sqrt(dx * dx + dy * dy);
    }
    
    int intersect(Point p1, Point q1, Point p2, Point q2, double* ix, double* iy) {
        double a = q1.x - p1.x;
        double b = -(q2.x - p2.x);
        double c = q1.y - p1.y;
        double d = -(q2.y - p2.y);
        double e = p2.x - p1.x;
        double f = p2.y - p1.y;
        double det = a * d - b * c;
        if (fabs(det) < EPS) return 0;
        double s = (d * e - b * f) / det;
        double t = (-c * e + a * f) / det;
        if (s < EPS || s > 1 - EPS || t < EPS || t > 1 - EPS) return 0;
        *ix = p1.x + s * (q1.x - p1.x);
        *iy = p1.y + s * (q1.y - p1.y);
        if (equal((Point){*ix, *iy}, p1) || equal((Point){*ix, *iy}, q1) ||
            equal((Point){*ix, *iy}, p2) || equal((Point){*ix, *iy}, q2))
            return 0;
        return 1;
    }
    
    void add_edge(int u, int v, double cost) {
        Edge* e = malloc(sizeof(Edge));
        e->to = v;
        e->cost = cost;
        e->next = graph[u];
        graph[u] = e;
    }
    
    int cmp_point(const void* a, const void* b) {
        Point* p = (Point*)a;
        Point* q = (Point*)b;
        if (fabs(p->x - q->x) > EPS) return p->x < q->x ? -1 : 1;
        if (fabs(p->y - q->y) > EPS) return p->y < q->y ? -1 : 1;
        return 0;
    }
    
    int get_node_index(Point p) {
        for (int i = 0; i < node_count; i++) {
            if (equal(points[i], p)) return i;
        }
        points[node_count++] = p;
        return node_count - 1;
    }
    
    void build_graph(int M) {
        for (int i = 0; i < M; i++) {
            Point p1 = points[segs[i].a];
            Point p2 = points[segs[i].b];
            Point seg_pts[MAXN];
            int count = 0;
            seg_pts[count++] = p1;
            seg_pts[count++] = p2;
    
            for (int j = 0; j < M; j++) {
                if (i == j) continue;
                double ix, iy;
                if (intersect(p1, p2, points[segs[j].a], points[segs[j].b], &ix, &iy)) {
                    seg_pts[count++] = (Point){ix, iy};
                }
            }
    
            qsort(seg_pts, count, sizeof(Point), cmp_point);
            for (int k = 0; k < count - 1; k++) {
                int u = get_node_index(seg_pts[k]);
                int v = get_node_index(seg_pts[k + 1]);
                double d = distance(seg_pts[k], seg_pts[k + 1]);
                add_edge(u, v, d);
                add_edge(v, u, d);
            }
        }
    }
    
    int parse_node(char* s, int N) {
        if (s[0] == 'C') {
            int id = atoi(s + 1);
            if (id < 1 || id > node_count - N) return -1;
            return N + id - 1;
        } else {
            int id = atoi(s);
            if (id < 1 || id > N) return -1;
            return id - 1;
        }
    }
    
    // k最短路を求めるダイクストラ法
    void k_shortest_paths(int start, int k) {
        PriorityQueue pq;
        heap_init(&pq);
        
        // 初期化
        for (int i = 0; i < node_count; i++) {
            path_count[i] = 0;
            for (int j = 0; j < k; j++) {
                dist_k[i][j] = INF;
            }
        }
        
        // 始点の処理
        dist_k[start][0] = 0;
        path_count[start] = 1;
        heap_push(&pq, start, 0);
        
        while (pq.size > 0) {
            HeapNode cur = heap_pop(&pq);
            int v = cur.node;
            double d = cur.dist;
            
            // このパスがk番目よりも悪い場合はスキップ
            if (path_count[v] > 0 && path_count[v] < k && d > dist_k[v][path_count[v]-1]) {
                continue;
            }
            
            // 各隣接ノードを処理
            for (Edge* e = graph[v]; e; e = e->next) {
                int to = e->to;
                double new_dist = d + e->cost;
                
                // 新しいパスがk番目までの既存パスより良い場合、挿入
                if (path_count[to] < k || new_dist < dist_k[to][k-1]) {
                    // 挿入位置を見つける
                    int pos = path_count[to];
                    while (pos > 0 && new_dist < dist_k[to][pos-1]) {
                        if (pos < k) {
                            dist_k[to][pos] = dist_k[to][pos-1];
                        }
                        pos--;
                    }
                    
                    // 新しいパスを挿入
                    if (pos < k) {
                        dist_k[to][pos] = new_dist;
                        if (path_count[to] < k) path_count[to]++;
                        heap_push(&pq, to, new_dist);
                    }
                }
            }
        }
    }
    
    int main() {
        int N, M, P, Q;
        scanf("%d %d %d %d", &N, &M, &P, &Q);
        for (int i = 0; i < N; i++) {
            scanf("%lf %lf", &points[i].x, &points[i].y);
        }
        node_count = N;
        for (int i = 0; i < M; i++) {
            scanf("%d %d", &segs[i].a, &segs[i].b);
            segs[i].a--; segs[i].b--;
        }
    
        build_graph(M);
    
        for (int q = 0; q < Q; q++) {
            char s1[10], s2[10];
            int k;
            scanf("%s %s %d", s1, s2, &k);
            int start = parse_node(s1, N);
            int goal = parse_node(s2, N);
            if (start == -1 || goal == -1) {
                printf("NA\n");
                continue;
            }
            
            // k値を制限
            if (k > MAX_K) k = MAX_K;
            
            k_shortest_paths(start, k);
            
            // k番目までの経路を出力
            if (path_count[goal] == 0) {
                printf("NA\n");
            } else {
                for (int i = 0; i < k && i < path_count[goal]; i++) {
                    if (dist_k[goal][i] == INF) {
                        printf("NA\n");
                    } else {
                        printf("%.5f", dist_k[goal][i]);
                        // 複数の結果を出力する場合は区切り文字を追加
                        if (i < path_count[goal] - 1 && i < k - 1) {
                            printf(" ");
                        }
                    }
                }
                printf("\n");
            }
        }
    
        // メモリ解放
        for (int i = 0; i < node_count; i++) {
            Edge* edge = graph[i];
            while (edge) {
                Edge* next = edge->next;
                free(edge);
                edge = next;
            }
        }
        return 0;
    }
    
  6. @Iyatsu

    Questioner

    しかし、出力が予想していたものとは異なっていたため、現在のコードに何か問題があればご指摘いただけると幸いです。
    入力
    6 5 0 2 0 0 2 5 4 7 5 5 7 1 9 5 1 4 1 6 2 5 3 5 4 6 1 4 5 C1 C3 10
    予想される出力
    7.07107 8.65895 8.99493 9.81418 12.7711 2.68432 3.83003 6.79648 9.46671 11.9 16.0121
    実際の出力
    7.07107 8.99493 10.15017 10.84230 11.28448 2.68432 4.97387 5.76342 6.45556 6.79648

  7. 予想される出力
    7.07107 8.65895 8.99493 9.81418 12.7711 2.68432 3.83003 6.79648 9.46671 11.9 16.0121

    11件出力されていますが、これはどこから「予想」したものでしょうか?
    MAX_K=5なら、出力は10件では?

  8. @Iyatsu

    Questioner

    プログラムの問題を解いていて現在行き詰っているという状況なんですが、k<=5にも関わらず出力例がなぜか11個になっていました。問題にも特に記載がなかったため、なぜ11個出力されるのかは自分にも分からないです。

  9. その「問題」は、どこかの書籍かネットにある情報でしょうか?
    所在を明らかにはできませんか?

    うえのコードを参考に自分が解くと、「想定」「実際」とも違う値でした。
    元の問題文を見てみたいです。

    7.07107 8.99493 10.15017 11.28448 11.33982 
    2.68432 6.45556 6.79648 6.95307 8.14272 
    
  10. @Iyatsu

    Questioner

    スクリーンショット (19).png
    この問題を解いています。

  11. C1 C3 がどの交点を表すのか、数え方の定義が不明です。

Your answer might help someone💌