実現したいこと
C言語でGreedy法を修正してほしいです。以下のような実行結果が出力されるようにしたいです。
以下は理想の実行結果です。
1-3: 165.4 0-3: 185.3 0-1: 449.1 1-4: 617.7 0-2: 750.4 2-3: 855.6 3-4: 871.2 1-2: 1015.1 0-4: 1055.9 2-4: 1592.3 //1-3を追加後の次数 点0の次数:0 点1の次数:1 点2の次数:0 点3の次数:1 点4の次数:0 //0-3を追加後の次数 点0の次数:1 点1の次数:1 点2の次数:0 点3の次数:2 点4の次数:0 //0-1を追加後の次数 点0の次数:1 点1の次数:1 点2の次数:0 点3の次数:2 点4の次数:0 //1-4を追加後の次数 点0の次数:1 点1の次数:2 点2の次数:0 点3の次数:2 点4の次数:1 //0-2を追加後の次数 点0の次数:2 点1の次数:2 点2の次数:1 点3の次数:2 点4の次数:1 //2-3を追加後の次数 点0の次数:2 点1の次数:2 点2の次数:1 点3の次数:2 点4の次数:1 //3-4を追加後の次数 点0の次数:2 点1の次数:2 点2の次数:1 点3の次数:2 点4の次数:1 //1-2を追加後の次数 点0の次数:2 点1の次数:2 点2の次数:1 点3の次数:2 点4の次数:1 //0-4を追加後の次数 点0の次数:2 点1の次数:2 点2の次数:1 点3の次数:2 点4の次数:1 //1-4を追加後の次数 点0の次数:2 点1の次数:2 点2の次数:2 点3の次数:2 点4の次数:2 Final Path: 1 -> 3 -> 0 -> 2 -> 4 -> 1 Total Distance: 3311.1
前提
Greedy法でグラフの最短経路を見つけるプログラムを書いています。
アルゴリズムは、
①すべての辺の重みを昇順に並べる
②からの閉路からはじめ、辺の重みが小さい順に調べ、その辺が以下の2つの条件を満たすならば閉路を付け加える。
条件1.点の次数が3をこえない
条件2.すべての点を回らないような閉路はつくらない
です。
条件1まではできたのですが、条件2の閉路を見つけるのに苦労しています。もしよろしければ、以下のコードをもとに修正案を具体的に下さると助かります。もし、不明点がありましたら、コメント下さい。よろしくお願いします。
以下のプログラムのdfs関数やvalid関数がおかしいのは分かってはいるのですが、何度修正してもできませんでした。
#include <stdio.h> #include <stdlib.h> #include <string.h> #define MAX_CITIES 20 //最大都市数 #define INF 10000 int path[MAX_CITIES]; //現在の経路 int min_path[MAX_CITIES]; //最短距離の経路 int **circuit; //閉路 double dist[100]; //すべての辺の距離 int n; //点の総数 int m; //辺の数 double min_dist = INF; //最短距離 double total_dist = 0; int visited[MAX_CITIES]; //訪問済みの点 int count = 0; typedef struct { int u; //点1 int v; //点2 double dist; //点間の距離 } Adjacent; // 比較関数 int compare(const void *a, const void *b) { if (((Adjacent *)a)->dist > ((Adjacent *)b)->dist) { return 1; //昇順 } else if (((Adjacent *)a)->dist < ((Adjacent *)b)->dist) { return -1; } else { return 0; } } //すべて点の次数を計算する関数 int *degree(void) { int i, j; int *degree = (int *)malloc(sizeof(int) * n); //各点の次数 // すべての点の次数を求める for (i = 0; i < n; i++) { degree[i] = 0; for (j = 0; j < n; j++) { degree[i] += circuit[i][j]; } printf("点%dの次数:%d\n", i, degree[i]); } printf("\n"); return degree; } //dfs(今いるノード, 一つ前にいたノード) int dfs(const int current, const int before) { visited[current] = 1; for (int i = 0; i < n; i++) { if (circuit[current][i] == before) { printf("operate!1\n"); // 前のノードに戻る場合 continue; } if (visited[i]) { // 次に行くノードが過去に通ったことがある場合 printf("operate!2\n"); return 1; } printf("current:%d, i:%d\n", current, i); dfs(i, current); } return 0; } //閉路が条件を満たしているかを確かめる関数 int valid(int start) { int i; int *deg; //条件1:都市の次数が3を越えない deg = degree(); for (i = 0; i < n; i++) { if (deg[i] > 2) { return 0; // 条件1を満たさない場合は0を返す } } // 条件2: すべての都市を回る閉路があるかチェック for (i = 0; i < n; i++) { visited[i] = 0; // 訪問済みのフラグをリセット } if (dfs(start, -1)) { return 1; // 条件2を満たす閉路が存在する場合は1を返す } return 0; } // greedy法 void greedy(Adjacent *adjacent) { int i; int n1, n2; int path_idx = 0; // 経路のインデックスを初期化 for (i = 0; i < m; i++) { n1 = adjacent[i].u; n2 = adjacent[i].v; circuit[n1][n2] = 1; circuit[n2][n1] = 1; if (valid(n1) == 0) { // 条件を満たさない閉路を作った場合は辺を削除 circuit[n1][n2] = 0; circuit[n2][n1] = 0; } else { total_dist += adjacent[i].dist; // 距離を加算 path[path_idx++] = n1; // 経路に点n1を追加 } } // 最後に始点を追加して閉路を完成させる path[path_idx] = 0; total_dist += adjacent[0].dist; // 始点から始点への距離を加算 } int main(void) { FILE *fp; int i, j; Adjacent adjacent[100]; printf("operate!\n"); if ((fp = fopen("network.txt", "r")) == NULL) { fprintf(stderr, "File open error.\n"); exit(1); } // 点の数を取得 fscanf(fp, "%d", &n); // 閉路を格納する2次元ポインタの隣接行列を作る circuit = (int **)malloc(sizeof(int *) * MAX_CITIES); for (i = 0; i < n; i++) { circuit[i] = malloc(sizeof(int) * MAX_CITIES); for (j = 0; j < n; j++) { circuit[i][j] = 0; } } // 隣接する辺を生成 while (fscanf(fp, "%d %d %lf", &adjacent[m].u, &adjacent[m].v, &adjacent[m].dist) != EOF) { m++; } fclose(fp); // アークを長さの昇順でソートする qsort(adjacent, m, sizeof(Adjacent), compare); for (i = 0; i < m; i++) { printf("%d-%d: %.1lf\n", adjacent[i].u, adjacent[i].v, adjacent[i].dist); } greedy(adjacent); // 最終的な経路を表示 printf("Final Path: "); for (i = 0; i < n; i++) { printf("%d", path[i]); if (i < n - 1) { printf(" -> "); } } printf("\n"); printf("Total Distance: %.1lf\n", total_dist); return 0; }
実行結果です。点0、点1、点3で0のときに閉路になっているので、greedy関数内の辺を追加を取り消すプログラムが動かずに、そのごもそのまま計算されてしまいます。
operate! 1-3: 165.4 0-3: 185.3 0-1: 449.1 1-4: 617.7 0-2: 750.4 2-3: 855.6 3-4: 871.2 1-2: 1015.1 0-4: 1055.9 2-4: 1592.3 点0の次数:0 点1の次数:1 点2の次数:0 点3の次数:1 点4の次数:0 current:1, i:0 operate!2 operate!2 点0の次数:1 点1の次数:1 点2の次数:0 点3の次数:2 点4の次数:0 operate!2 点0の次数:2 点1の次数:2 点2の次数:0 点3の次数:2 点4の次数:0 operate!2 点0の次数:2 点1の次数:3 点2の次数:0 点3の次数:2 点4の次数:1 点0の次数:3 点1の次数:2 点2の次数:1 点3の次数:2 点4の次数:0 点0の次数:2 点1の次数:2 点2の次数:1 点3の次数:3 点4の次数:0 点0の次数:2 点1の次数:2 点2の次数:0 点3の次数:3 点4の次数:1 点0の次数:2 点1の次数:3 点2の次数:1 点3の次数:2 点4の次数:0 点0の次数:3 点1の次数:2 点2の次数:0 点3の次数:2 点4の次数:1 点0の次数:2 点1の次数:2 点2の次数:1 点3の次数:2 点4の次数:1 current:2, i:0 operate!2 current:2, i:1 operate!2 operate!2 Final Path: 1 -> 0 -> 0 -> 2 -> 0 Total Distance: 2557.5
network.txt
5 0 1 449.1 0 2 750.4 0 3 185.3 0 4 1055.9 1 2 1015.1 1 3 165.4 1 4 617.7 2 3 855.6 2 4 1592.3 3 4 871.2
試したこと
一応自分なりに考えた閉路を求めるアルゴリズムは、
①追加する辺の両端の点を始点と終点し、あらかじめ訪問済みにする。
②始点から行き止まりまで探索し、最後の値を戻り値として返す。
③②と同様に終点から行き止まりまで探索し、最後の値を戻り値として返す。
④もし、始点からの戻り値と終点からの戻り値が一致すれば閉路があるとし、値が一致しなければ閉路はないする。
こんな感じのアルゴリズムを考えてみたのですが実装が上手くいかず困っていたので質問しました。
