ダイクストラ法, 与えられたグラフにおいて, ある頂点からそれ以外の各頂点への最短のパス(ルート)を求めるアルゴリズムの一つです.
- グラフの頂点を, 到達済み頂点のグループと, 未到達頂点のグループへ分割します. 到達済み頂点のグループの初期値は, 開始頂点1つからなるグループで, 未到達の頂点グループの初期値は, それ以外の頂点すべてを含んだグループになります.
- 「到達済み頂点のグループのうち, どれかの頂点」(=vn)との間に「最も短い辺を持つ未到達の頂点」(=vm)を到達済み頂点のグループへ追加する. また, この辺を新しいルートとする. (開始点から新しく追加されたvmまでのパスは, 「開始点~vnまでのパス + 新しく追加された辺」となります.)
- 未到達頂点のグループに要素がなければアルゴリズム終了. 未到達頂点のグループに要素があれば, 2へ.
日本語で書くとこんな感じで, 未到達の頂点が1つづつ減っていって, すべての頂点へのルートが決定します.
なんとなく階層的クラスタリングに似ている気もしますが. 以下のような感じでダイクストラ法の実行結果が得られます. a~jがグラフ中の各点で隣の数字が開始点からの最短距離で, 赤線が開始点からの各点へのルートを表します. 黒線は, グラフに含まれているが, 最短ルートには含まれない辺. 例えば, c→jのルートは, c → d → a → j となります.
以下の図において, 各点は, (x,y)座標が与えられており, 各点間の距離(=辺の長さ)は二乗距離で定義されます. (つまり, 視覚的な距離と, 頂点間の距離(辺の長さ)がおおよそ対応しています.)
ナイーブなダイクストラ法の実装が以下のコードです. ダイクストラ法本体の実装より, グラフと実行結果可視化のコードの方がなぜか, 分量が多いですが.
(use '[clojure.core.match :only (match)]
'[clojure.math.numeric-tower :as math])
;;---------------------------------------------------------------
;; find path
(defn mean-square [x1 x2 y1 y2]
(math/sqrt (+ (* (- x1 x2) (- x1 x2)) (* (- y1 y2) (- y1 y2)))))
(defn get-weights [routes points-with-coord]
(defn get-distance
[point-A point-B]
(let [a (get points-with-coord point-A)
b (get points-with-coord point-B)]
(mean-square (first a) (first b) (second a) (second b))))
(->> routes
(map #(get-distance (first %) (second %)))
(interleave routes)
(apply hash-map)))
(defn get-neighbors [routes]
(->> (concat (map reverse routes) routes)
(map vec)
sort
(group-by first)
(map (fn [x] (list (first x) (map second (second x)))))
(reduce concat)
(apply hash-map)))
(defn find-path [start routes vs weights]
(defn length [u v]
(weights (sort (list u v))))
(defn get-dist [x dp]
(get-in dp [x :dist]))
(defn find-nearest [xs dp]
(->> (map (fn [x] [(get-dist x dp) x]) xs)
sort first second))
(defn short-route? [u v dp]
(< (+ (length u v) (get-dist u dp)) (get-dist v dp)))
(defn set-shorter [u v dp]
(assoc-in dp [v] {:dist (+ (get-dist u dp) (length u v)) :prev u}))
(defn update-at [u dp v]
(if (short-route? u v dp) (set-shorter u v dp) dp))
(let [distance-previous-data
(->> vs
(map #(list % {:dist Double/POSITIVE_INFINITY :prev nil}))
(apply concat) (apply hash-map)
(#(assoc-in % [start] {:dist 0 :prev nil})))]
(loop [q vs dp distance-previous-data]
(if (empty? q)
dp
(let [u (find-nearest q dp)
new (reduce (partial update-at u) dp (u (get-neighbors routes)))]
(recur (remove #(= u %) q) new))))))
;;---------------------------------------------------------------
(def spwc ;;sample-points-with-coord
{:a [20 80] :b [35 20] :c [80 40] :d [60 100] :e [70 10]
:f [10 60] :g [120 80] :h [20 35] :i [110 20] :j [0 120]})
(def sample-routes
(let [routes
[[:j :f] [:j :a] [:f :a] [:f :h]
[:a :d] [:d :g] [:g :c] [:c :d]
[:g :i] [:c :i] [:c :e] [:e :i]
[:a :h] [:h :b] [:b :e] [:b :c]]]
(map sort routes)))
;; samples
;; (find-path :j sample-routes (keys spwc) (get-weights sample-routes spwc))
;; (find-path :c sample-routes (keys spwc) (get-weights sample-routes spwc))
;;---------------------------------------------------------------
;; graph plot
(import (javax.swing JFrame))
(import (java.awt Color))
(import (java.awt Graphics))
(import (java.awt Graphics2D))
(import (java.awt Font))
(def radius 8)
(defn x-extend [x]
(+ (* (- x radius) 3) 50))
(defn y-extend [y]
(+ (- (* (- y radius) 3)) 400))
(defn vertex->str [key]
(apply str (rest (str key))))
;; (plot-graph spwc sample-routes)
(defn plot-graph [points route-pairs answer]
(let [window-size 450
font (Font. "Monospace" Font/BOLD 14)
frame (JFrame. "Route")
get-x #(+ radius (x-extend (first %1))) ;; for draw-line
get-y #(+ radius (y-extend (second %1)))] ;; for draw-line
(doto frame
(.setSize window-size window-size)
(.setVisible true)
(.setResizable false))
(Thread/sleep 100) ;; wait for the generateion of Window
(def graphics (.. frame (getGraphics)))
(defn plot-point [name]
(let [coord (name points)
r (* radius 2)
x-pos (x-extend (first coord))
y-pos (y-extend (second coord))]
(doto (cast Graphics2D graphics)
(.setColor (Color. 255 100 100))
(.fillOval x-pos y-pos r r)
(.setColor (Color. 100 190 190))
(.drawOval x-pos y-pos r r)
(.setColor Color/blue)
(.setFont font)
(.drawString
(->> answer name :dist round (str (vertex->str name) "="))
(+ x-pos 20) (+ y-pos 10)))))
(defn draw-line [point-a point-b]
(let [a (point-a points) b (point-b points)]
(if (or (= point-b (:prev (point-a answer))) ;; from b to a
(= point-a (:prev (point-b answer)))) ;; from a to b
(.setColor graphics Color/red)
(.setColor graphics Color/black))
(.drawLine graphics (get-x a) (get-y a) (get-x b) (get-y b))))
;; draw background
(doto graphics
(.setColor Color/white)
(.fillRect 0 0 window-size window-size))
;; draw route
(doall (map draw-line (map first route-pairs) (map second route-pairs)))
;; draw points
(doall (map plot-point (keys points)))
(println "done!")))
;;---------------------------------------------------------------
;; execution example
(def from-a (find-path :a sample-routes (keys spwc) (get-weights sample-routes spwc)))
(def from-c (find-path :c sample-routes (keys spwc) (get-weights sample-routes spwc)))
(def from-e (find-path :e sample-routes (keys spwc) (get-weights sample-routes spwc))) こんな感じになりました. find-path関数で, 最短ルートとその距離を求めます. find-pathの一番目の引数は, 開始点. get-weights関数は, 各辺の重み(=距離)を計算しています. plot-graphでSwingにより, グラフを表示します.以下のように実行します.
user> (load-file "find-path.clj") #'user/from-e user> (plot-graph spwc sample-routes from-e) done! nil user> (plot-graph spwc sample-routes from-a) done! nilというわけで実行結果. 開始点をaとすると,
となり, 開始点をeとすると,
こんな感じ.
0 件のコメント :
コメントを投稿