teratail header banner
teratail header banner
質問するログイン新規登録

回答編集履歴

4

加筆

2023/11/27 22:57

投稿

ikedas
ikedas

スコア4441

answer CHANGED
@@ -19,6 +19,7 @@
19
19
 
20
20
  【追記】ちなみに、手元で書いてみたら少なくとも2.はできました。さらに3.の改良をするのも簡単そうです。
21
21
 
22
+ コード
22
23
  ---
23
24
  なんかコードを載せた回答が出てしまったのでもういいや。2.のコードで私が書いたものも載せときますね。
24
25
  ```go
@@ -144,7 +145,9 @@
144
145
  visited = visited[:len(visited)-1]
145
146
  }
146
147
  ```
148
+ 言語はGoです。経路探索の主要部であるfindResults()関数では、luuguasさんも言及されている「深さ優先探索」を、再帰呼び出しを用いて行っています。
149
+
147
- 言語はGoです。実行結果:
150
+ 実行結果:
148
151
  ```
149
152
  $ go run main.go
150
153
  出発駅……駅0

3

コードを載せますが、せめてC#に書き直すのだけは自分でやってね。

2023/11/27 14:35

投稿

ikedas
ikedas

スコア4441

answer CHANGED
@@ -17,4 +17,164 @@
17
17
 
18
18
  今はいきなり3. をやろうとしている節もありますが、2.のコードを書いてから3.の改良をやればコードの見通しもよくなり、うまくいくのではないでしょうか。
19
19
 
20
- 【追記】ちなみに、手元で書いてみたら少なくとも2.はできました。さらに3.の改良をするのも簡単そうです。
20
+ 【追記】ちなみに、手元で書いてみたら少なくとも2.はできました。さらに3.の改良をするのも簡単そうです。
21
+
22
+ ---
23
+ なんかコードを載せた回答が出てしまったのでもういいや。2.のコードで私が書いたものも載せときますね。
24
+ ```go
25
+ package main
26
+
27
+ import (
28
+ "bufio"
29
+ "fmt"
30
+ "os"
31
+ )
32
+
33
+ var stations = []string{
34
+ "駅0",
35
+ "駅1",
36
+ "駅2",
37
+ "駅3",
38
+ "駅4",
39
+ "駅5",
40
+ }
41
+
42
+ var lines = [][]int{
43
+ {0, 1, 2},
44
+ {3, 1, 4, 2},
45
+ {4, 5},
46
+ }
47
+
48
+ var nextStations = [][]int{}
49
+
50
+ var results = [][]int{}
51
+
52
+ func main() {
53
+ // 準備: 隣接駅のリストを作っておく
54
+ for s := 0; s < len(stations); s++ {
55
+ nextStations = append(nextStations, listNextStations(s))
56
+ }
57
+
58
+ // 出発駅と到着駅を入力
59
+ scanner := bufio.NewScanner(os.Stdin)
60
+
61
+ fmt.Print("出発駅……")
62
+ scanner.Scan()
63
+ start := scanner.Text()
64
+ fmt.Print("到着駅……")
65
+ scanner.Scan()
66
+ goal := scanner.Text()
67
+
68
+ s := -1
69
+ g := -1
70
+ for i, v := range stations {
71
+ switch v {
72
+ case start:
73
+ s = i
74
+ case goal:
75
+ g = i
76
+ }
77
+ if 0 <= s && 0 <= g {
78
+ break
79
+ }
80
+ }
81
+
82
+ // 結果を得る
83
+ findResults(s, g, []int{})
84
+
85
+ fmt.Println(len(results), " routes")
86
+ fmt.Println(results)
87
+ }
88
+
89
+ // 駅 s に隣接するすべての駅を列挙する
90
+ func listNextStations(s int) []int {
91
+ var next = []int{}
92
+
93
+ for _, line := range lines {
94
+ for i, station := range line {
95
+ if s == station {
96
+ if 0 < i {
97
+ next = append(next, line[i-1])
98
+ }
99
+ if i < len(line)-1 {
100
+ next = append(next, line[i+1])
101
+ }
102
+ }
103
+ }
104
+ }
105
+
106
+ return next
107
+ }
108
+
109
+ // 駅 s から最終目的の駅 g までの経路を探索し、
110
+ // 見つかったものを配列 results に加える。
111
+ // visited はこれまでに通ってきた駅の配列 (s が出発駅なら空)。
112
+ func findResults(s, g int, visited []int) {
113
+ // 経路を一駅進める
114
+ visited = append(visited, s)
115
+
116
+ // 隣接する駅をすべて調べる
117
+ for _, n := range nextStations[s] {
118
+ // ゴールなら経路を記録して、その先はもう調べない
119
+ if n == g {
120
+ result := make([]int, len(visited)+1)
121
+ copy(result, append(visited, n))
122
+ results = append(results, result)
123
+ continue
124
+ }
125
+
126
+ // すでに通った駅ならもう調べない
127
+ var passed bool
128
+ for _, v := range visited {
129
+ if n == v {
130
+ passed = true
131
+ break
132
+ }
133
+ }
134
+ if passed {
135
+ continue
136
+ }
137
+
138
+ // 上記のいずれでもなければ、次の駅に進んで探索を続行
139
+ findResults(n, g, visited)
140
+ }
141
+ // すべての隣接駅を調べ終わったら (再帰) 関数の呼び出しから戻る
142
+
143
+ // 経路を一駅戻す
144
+ visited = visited[:len(visited)-1]
145
+ }
146
+ ```
147
+ 言語はGoです。実行結果:
148
+ ```
149
+ $ go run main.go
150
+ 出発駅……駅0
151
+ 到着駅……駅5
152
+ 2 routes
153
+ [[0 1 2 4 5] [0 1 4 5]]
154
+ $ go run main.go
155
+ 出発駅……駅1
156
+ 到着駅……駅5
157
+ 2 routes
158
+ [[1 2 4 5] [1 4 5]]
159
+ $ go run main.go
160
+ 出発駅……駅2
161
+ 到着駅……駅5
162
+ 2 routes
163
+ [[2 1 4 5] [2 4 5]]
164
+ $ go run main.go
165
+ 出発駅……駅3
166
+ 到着駅……駅5
167
+ 2 routes
168
+ [[3 1 2 4 5] [3 1 4 5]]
169
+ $ go run main.go
170
+ 出発駅……駅3
171
+ 到着駅……駅0
172
+ 1 routes
173
+ [[3 1 0]]
174
+ $ go run main.go
175
+ 出発駅……駅4
176
+ 到着駅……駅0
177
+ 2 routes
178
+ [[4 1 0] [4 2 1 0]]
179
+ # 以後は上記の逆順の結果になるだけなので省略。
180
+ ```

2

できた

2023/11/27 07:48

投稿

ikedas
ikedas

スコア4441

answer CHANGED
@@ -16,3 +16,5 @@
16
16
  そのため、2. で実装した単純な経路列挙アルゴリズムは改良の余地があります。経路探索の途中で、あきらかに最良ではない経路 (すでにみつかった経路よりも乗り換え数が多くなる、など) がでてきたら探索を打ち切ってほかの候補を探す、などの工夫が必要です。
17
17
 
18
18
  今はいきなり3. をやろうとしている節もありますが、2.のコードを書いてから3.の改良をやればコードの見通しもよくなり、うまくいくのではないでしょうか。
19
+
20
+ 【追記】ちなみに、手元で書いてみたら少なくとも2.はできました。さらに3.の改良をするのも簡単そうです。

1

微加筆

2023/11/27 02:35

投稿

ikedas
ikedas

スコア4441

answer CHANGED
@@ -10,7 +10,7 @@
10
10
  今回の場合だと、質問に明示されていない暗黙の条件として「一度通った駅は二度と通らない」というものがあるはずです。ですから再帰の終了条件として「すでに調べた駅に到達したら、それ以上調べずに関数を終了する」というコードが関連の関数に必要ですが、それが抜けている (書くべき場所に書かれていない) と思われます。
11
11
 
12
12
  2. 経路の探索と乗り換え回数のカウントの両方を一度にやろうとしているため、ミスが混入しやすいです。今後経由駅数などほかの条件を適用したい場合にコードの変更も難しいです。
13
- まずは一つの関数で、「出発駅と到着駅が与えられたときに、あり得る経路をすべて列挙する」というコードを書いてみてはどうですか。もちろん再帰呼び出しを使ってもかまいません。
13
+ まずは一つの関数で、「出発駅と到着駅が与えられたときに、あり得る経路をすべて列挙する」(乗り換え数などの条件は加味しない) というコードを書いてみてはどうですか。もちろん再帰呼び出しを使ってもかまいません。
14
14
 
15
15
  3. 一般の場合の経路探索では「組み合わせ爆発」が起きる可能性があります (参考: 「[フカシギの数え方](https://youtu.be/Q4gTV4r0zRs)」)。都市開発ゲームということなので、プレイヤーがそのようなことをひき起こす路線を敷いてしまうこともありえますね。
16
16
  そのため、2. で実装した単純な経路列挙アルゴリズムは改良の余地があります。経路探索の途中で、あきらかに最良ではない経路 (すでにみつかった経路よりも乗り換え数が多くなる、など) がでてきたら探索を打ち切ってほかの候補を探す、などの工夫が必要です。