Problem 81 - 最短経路問題

きました。最短経路。

Problem 81 - PukiWiki

下記の5次の正方行列で、左上のセルから開始し右下のセルで終わる道を探索する。
ただし下方向と右方向にのみ移動できるものとする。一番小さなパスは下で赤で示されたものである。
このときの合計は2427になる。

131	673	234	103	18
201	96	342	965	150
630	803	746	422	111
537	699	497	121	956
805	732	524	37	331

今、31Kのテキストファイルmatrix.txtには80×80の行列が書かれている. 
左上のセルから開始し右下のセルで終わりかつ右方向と下方向にのみ移動するときの最小の道の和を求めよ.

4*4行列での経路数は24本。

んじゃ、80*80の経路数はいくつになるかって考える。


n*n行列の経路数は、点がn^2個あって、経路が2本伸びていたと考えると、2n^2。末端は経路が無いので、2n本ひいてやると、2n(n - 1)

つまり、80*80行列の場合、2 * 80 (80 - 1)で12640経路あるわけ。「経路数だけで1万経路」

経路の組合せはあわわわわわ・・・・普通に再帰してたら終わらない。


ということでダイクストラアルゴリズムのちょい改造版。

  • 最初は131なので、671,201に131を足してリストに加える(802 331)。(リストは位置つき)
  • 最小の331を選ぶ。96,630に331を足してリストに加える(802 437 961)。
    • もしも同じ位置だった場合は比較して低いほうを選択する。
  • 続ける。

ダイクストラアルゴリズムはO(n^2)だから・・・お・終わるかな・・・。


ま、やってみる。

(define (problem81 data)
  (letrec ((y-len (vector-length data))
           (x-len (vector-length (vector-ref data 0)))
           (get (lambda (x y) (if (or (>= x x-len) (>= y y-len))
                                  999999999999999999999999 ; 無限
                                  (vector-ref (vector-ref data y) x))))
           (remove-min (lambda (s cont)
                         (letrec ((iter (lambda (x m acc)
                                          (if (null? x)
                                              (cont m acc)
                                              (if (< (cdar x) (cdr m))
                                                  (iter (cdr x) (car x) (cons m acc))
                                                  (iter (cdr x) m (cons (car x) acc)))))))
                           (iter (cdr s) (car s) '()))))
           (update (lambda (x y sum l)
                     (let ((res (assoc (cons x y) l))
                           (new (+ (get x y) sum)))
                       (if res
                           (begin
                             (if (< new (cdr res))
                                 (set-cdr! res new)) ; update
                             l)
                           (cons (cons (cons x y) new)
                                 l))))) ; new
           (update2 (lambda (m l)
                     (let ((x (caar m))
                           (y (cdar m))
                           (sum (cdr m)))
                       (update x (+ y 1) sum
                               (update (+ x 1) y sum l)))))
          ; (iter (lambda (l end)
          ;         (remove-min l
          ;                     (lambda (m acc)
          ;                       (if (equal? (car m) end)
          ;                           m
          ;                           (iter (update2 m acc)
          ;                                 end))))))
           (iter (lambda (l end)
                   (let ((e (assoc end l)))
                     (or e (iter (remove-min l update2) end))))))
    (cdr (iter (list (cons '(0 . 0) (get 0 0)))
               (cons (- x-len 1) (- y-len 1))))))

(problem81 *data81*) ; 427337

本当の最短経路ではないかも知れないけど、とりあえず末端まで行ったら計算を打ち切ることにした。計算時間は100秒くらい。

コメントアウトしてあるバージョンだと終わらないのだ。


最小の値を求めるのに時間がかかっているっぽいので、順序付きリストで挿入すればまだまだ早くなりそう。