質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Ruby

Rubyはプログラミング言語のひとつで、オープンソース、オブジェクト指向のプログラミング開発に対応しています。

アルゴリズム

アルゴリズムとは、定められた目的を達成するために、プログラムの理論的な動作を定義するものです。

Q&A

解決済

1回答

1614閲覧

「プログラマ脳を鍛える数学パズル」の Q59 について教えて下さい

pyxis

総合スコア16

Ruby

Rubyはプログラミング言語のひとつで、オープンソース、オブジェクト指向のプログラミング開発に対応しています。

アルゴリズム

アルゴリズムとは、定められた目的を達成するために、プログラムの理論的な動作を定義するものです。

1グッド

0クリップ

投稿2020/01/15 17:27

編集2020/01/15 19:03

「プログラマ脳を鍛える数学パズル」という本に載っている
「Q59 ハンカチ落としの総走行距離」の解答コードで腑に落ちない部分があるので
どなたか教えていただけないでしょうか?

問題は以下です。
長文のためキャプチャ画像である事ご容赦下さい。

イメージ説明

解答のコードは以下です

Ruby

1@n = 8 2# 鬼を含んだ配列。 3@all = (0..@n).to_a 4 5#開始時の位置は1を固定した1通り。終了は1を順にずらしたモノ全て。 6 7# 開始時の配置 8start = {} 9start[(1..@n).to_a] = [] 10 11# 終了時の配置 12goal = {} 13@n.times{|i| 14 goal[(1..@n).to_a.reverse.rotate(i)] = [] 15} 16 17# 移動距離を求める 18def dist(log) #log:ハンカチを落とした位置の記録 19 return 0 if log.size == 0 20 check = log.clone 21 pre = check.shift 22 sum = @n + pre 23 check.each{|c| 24 # %@n することにより0をまたぐ場合とそうでない場合をまとめて書ける 25 sum += @n + (c + @n - pre) % @n 26 pre = c 27 } 28 sum 29end 30 31# 探索(directionがtrueのとき順方向、falseのとき逆方向) 32def search(child, direction) 33 child.clone.each{|key, value| 34 oni = (@all - key)[0] # 使われていないのが鬼 35 @n.times{|i| #全ての位置に鬼を座らせる 36 k = key.clone 37 k[i] = oni #新たに鬼が座った配置 38 v = value + [i] #ハンカチを落とした位置のログ 39 if child.has_key?(k) then 40 if direction then # 順方向 41 child[k] = v if dist(v) < dist(child[k]) 42 else # 逆方向 43 child[k] = v if dist(v.reverse) < dist(child[k].reverse) 44 end 45 else 46 child[k] = v 47 end 48 } 49 } 50end 51 52cnt = 0 53while (start.keys & goal.keys).size == 0 do 54 if cnt % 2 == 0 then # 偶数のときは順方向に探索 55 search(start, cnt % 2 == 0) 56 else # 奇数のときは逆方向に探索 57 search(goal, cnt % 2 == 0) 58 end 59 cnt += 1 60end 61 62# 両方向からの探索結果が合流した場合、最短の移動距離を算出 63min = 98 64(start.keys & goal.keys).each{|c| 65 d = dist(start[c] + goal[c].reverse) 66 min = [min, d].min 67} 68 69puts min 70

幅優先探索でスタートとゴールから双方向探索して経路が重なるモノの中から最短になるモノを求めていることは
理解できました。
腑に落ちないのはsearch関数内の逆方向における次の部分です。

Ruby

1child[k] = v if dist(v.reverse) < dist(child[k].reverse)

ここで用いられているdist関数はスタート位置が0である設定で書かれているのですが、
実際に順方向から辿ってきた場合、逆方向のスタート位置は0とは限らず、
1から7のいずれかである場合もあります。
例えば0からスタートの場合

dist([0]) < dist([2])

ですが1からスタートの場合

dist([0]) > dist([2])

と大小関係が逆転し、距離に誤差が出ると思います。
その為に解答コードの最後の

Ruby

1(start.keys & goal.keys).each{|c| 2 d = dist(start[c] + goal[c].reverse) 3 min = [min, d].min 4}

では、

Ruby

1d = dist(start[c]) + dist(goal[c].reverse)

とせずに

Ruby

1d = dist(start[c] + goal[c].reverse)

と、ハンカチの落とした位置をスタートからゴールにまで新たに繋げ直して
dist関数に入れて正確な距離を出しているのだと思います。

なぜ

Ruby

1child[k] = v if dist(v.reverse) < dist(child[k].reverse)

では誤差を無視することが出来るのでしょうか?

Bearded-Ockham👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

自己解決

著者の方に逆方向の

dist(v.reverse) と dist(child[k].reverse)

はスタート位置により大小が逆転する場合があることを伝えたところ
修正を検討するとのことでした。

投稿2020/01/17 12:13

pyxis

総合スコア16

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2020/01/17 13:50 編集

・・あらら、どういう理屈なのか考えてたのに・・・本酷いですね。 探索の仕方がもの凄く無駄ですし(何故毎回distを最初から計算し直す) 「プログラム脳」ならこんな探索プログラム組むより一般項考えた方が早いんじゃなかろうかと思います。 以下一般項。 あってるとは思うのですが間違いがあったらどなたか指摘していただけると嬉しいです。 奇数でも偶数でも話はほとんど一緒なので偶数の場合だけ示すと N(偶数かつ4以上)人の場合入れ替えるべきペアはN/2-1で、1ペア入れ替えるのに合計3周。最初の0番の1周を加えて ⇒少なくとも3N(N/2-1)+Nの移動距離が必要 N/2-1ペアあるので、各ペアの入れ替え間に移動が必要、これがロスレスに行われても1周かかるので A[N] = 3N(N/2-1)+N+(N/2-2)*N=2N(N-2) あとは各ペアの入れ替え間移動をロスレスに行うことができるかの検討。 これは実際に可能で、例えばN=6であれば3と6を軸に反転するものとして 0⇒1(最初の移動), [023456], dist=6 1⇒4 (対角位置に移動), [023156], dist=15 4⇒2 (目的位置に移動), [043156], dist=25 2⇒1 (目的位置に移動), [043256], dist=33 1⇒5 (隣に移動, N=6の場合目的地), [043216], dist=40 5⇒0 (目的地に移動), [543216], dist=48 1だけ特殊な移動をしていますが、これが各ペアの入れ替え間の移動の役割を担っており、全体的に見ればロスレスになっています。 8以上の場合も同様に 1を対角に移動⇒入れ替え⇒1を隣に移動⇒入れ替え⇒1を隣に移動⇒入れ替え⇒・・・ で成立するはず。 奇数の場合は同様に考えて4N[N/2]かな・・・?([]は切り捨て) 結果的にN=1,2も成立しますね
pyxis

2020/01/18 07:14 編集

一般項を求める事ができるのですね、参考になりました。 奇数の場合を自分も考えてみたのですが、 色々最短になるパターンはあると思うのですがNotionalKettleさんの書かれた 偶数の時と似たところで考えると、1の対角の右隣の数字(5人なら3)を固定して 初めに1をその左隣に移動させる場合で考えました。 ・初めの0の移動距離が N ・入れ替えになるペア数が(N-1)/2、入れ替えの最短距離が3周分、一周の距離がNなので (N-1)/2 * 3 * N ・1をずらす回数が{(N-1)/2-1}回で、その度1周分の長さNが加算されるので N * {(N-1)/2-1} これらを足すと 2N(N-1) となり NotionalKettleさんが出された 4N[N/2] = 4N(N-1)/2 = 2N(N-1) (Nが奇数の場合) と一致し、この一般項で合ってると思います。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問