読者です 読者をやめる 読者になる 読者になる

ダイクストラ法

前回のアルゴリズムイントロダクション輪講の話題、単一始点最短路問題から。詳しくは アルゴリズムイントロダクション第24章 単一始点最短路問題 - naoyaのはてなダイアリー へ。その中で丁度前回 書いたプリム法と同じく、ダイクストラ法が最小優先度付きキューを使うので、ちょっといじったらかけるのでは?と思って書いてみました。(相変わらずの乱プログラムご容赦...)。対象のグラフは教科書通り。

実装的には、minheap クラスは前回のプリム法と全く一緒。MinPriorityQueue は前回と使い方が違うので一部実装し直し。(といっても relax の周り)。実行するとこんな感じになるはずです。

s -> y -> t  (8)
s -> y -> t -> x  (9)
s -> y  (5)
s -> y -> z  (7)
  • 教科書のヒープソート (6章) にもあったように、優先度付きキューの実装はそのヒープのキー値と実際のオブジェクトの紐付けが肝、という事を体感。
  • 使うデータ構造が近いと、なんとなく実装から香り立つ雰囲気も近い (?!)
  • 前回のコードをみて、点 (Vertex) のクラス名が Vertice になっている事に気づく。ハズカス。もうそのままにしときます。。
# -*- coding: utf-8 -*-

import sys
from heapq import heappush,heappop

class minheap:    
    
    def __init__(self,elems):
        self.heap = []
        [heappush(self.heap,e) for e in elems]
        
    def extract_min(self):
        return heappop(self.heap)
    
    def decrease_key(self,i,key):
        if key > self.heap[i] :
            raise Exception, "new key is larger than current key"
        self.heap[i] = key
        while i > 0 and self.heap[i/2] > self.heap[i] :
            pindex = i/2
            self.heap[i], self.heap[pindex] = self.heap[pindex], self.heap[i]
            i = pindex
            
    def index(self,key):
        return self.heap.index(key)
    
    def is_empty(self):
        return len(self.heap) == 0
    
class MinPriorityQueue:
    
    def __init__(self,elems):
        keys = map(lambda x : x.d, elems)
        self.minheap = minheap(keys)
        self.key_elem_handles = dict([(k,[]) for k in keys])
        [self._update_key_elem_handles(e) for e in elems]
        
    def is_phi(self):
        return self.minheap.is_empty()
    
    def extract_min(self):
        min_key = self.minheap.extract_min()    
        elem = self.key_elem_handles[min_key].pop(0)
        return elem
    
    def relax(self,u,v):
        if v.d > u.d + u.w(v) :
            self._update(v, u.d + u.w(v))
            v.pi = u 
    
    def _update(self,elem,d):        
        oldkey = elem.d
        i = self.minheap.index(oldkey)
        self.minheap.decrease_key(i, d)
        self.key_elem_handles[oldkey].remove(elem)
        elem.d = d
        self._update_key_elem_handles(elem)
    
    def _update_key_elem_handles(self,elem):
        key = elem.d
        if not self.key_elem_handles.has_key(key) :
            self.key_elem_handles[key] = []
        self.key_elem_handles[key].append(elem)

class Vertex:    
    
    def __init__(self,name):
        self.name = name
        self.adjs = dict()        
        
    def add(self,vertex,weight):
        if not self.adjs.has_key(vertex) :
            self.adjs[vertex] = weight
        return self
    
    def w(self,vertex):
        return self.adjs.get(vertex,sys.maxint)
    
    def adj(self):
        return self.adjs.keys()    

    def __repr__(self):
        return self.name
    
    __str__ = __repr__
    
def initialize_single_source(vertices,s):
    for v in vertices :
        v.d = sys.maxint
        v.pi = None
    s.d = 0
    
def paint_path(vertices,s,v, first=False):
    if v == s :
        print s,
    else :
        if v.pi is None :
            print "no route found from %s to %s" % s,v
        else :
            paint_path(vertices,s,v.pi)
            print "-> %s" % v,
            if first :
                print " (%d)" % v.d
                            
# vertices
s,t,x,y,z = Vertex('s'), Vertex('t'), Vertex('x'), Vertex('y'), Vertex('z')
vertices = [s,t,x,y,z]

s.add(t,10).add(y,5)
t.add(x,1).add(y,2)
x.add(z,4)
y.add(t,3).add(x,9).add(z,2)
z.add(s,7).add(x,6)

# DIJKSTRA
initialize_single_source(vertices, s)
Q = MinPriorityQueue(vertices)
while not Q.is_phi() :
    u = Q.extract_min()
    for v in u.adj() :
        Q.relax(u, v)

# output
for dest in [t,x,y,z] :    
    paint_path(vertices,s,dest,True)