配列を使用してpythonでダイクストラのアルゴリズムを実装しようとしています。これは私の実装です。
def extract(Q, w):
m=0
minimum=w[0]
for i in range(len(w)):
if w[i]<minimum:
minimum=w[i]
m=i
return m, Q[m]
def dijkstra(G, s, t='B'):
Q=[s]
p={s:None}
w=[0]
d={}
for i in G:
d[i]=float('inf')
Q.append(i)
w.append(d[i])
d[s]=0
S=[]
n=len(Q)
while Q:
u=extract(Q,w)[1]
S.append(u)
#w.remove(extract(Q, d, w)[0])
Q.remove(u)
for v in G[u]:
if d[v]>=d[u]+G[u][v]:
d[v]=d[u]+G[u][v]
p[v]=u
return d, p
B='B'
A='A'
D='D'
G='G'
E='E'
C='C'
F='F'
G={B:{A:5, D:1, G:2}, A:{B:5, D:3, E:12, F:5}, D:{B:1, G:1, E:1, A:3}, G:{B:2, D:1, C:2}, C:{G:2, E:1, F:16}, E:{A:12, D:1, C:1, F:2}, F:{A:5, E:2, C:16}}
print "Assuming the start vertex to be B:"
print "Shortest distances", dijkstra(G, B)[0]
print "Parents", dijkstra(G, B)[1]
答えは次のとおりです。
Assuming the start vertex to be B:
Shortest distances {'A': 4, 'C': 4, 'B': 0, 'E': 2, 'D': 1, 'G': 2, 'F': 4}
Parents {'A': 'D', 'C': 'G', 'B': None, 'E': 'D', 'D': 'B', 'G': 'D', 'F': 'E'}
しかし、私が得る答えはこれです:
Assuming the start vertex to be B:
Shortest distances {'A': 4, 'C': 4, 'B': 0, 'E': 2, 'D': 1, 'G': 2, 'F': 10}
Parents {'A': 'D', 'C': 'G', 'B': None, 'E': 'D', 'D': 'B', 'G': 'D', 'F': 'A'}.
ノードFの場合、プログラムは誤った答えを出します。誰かが理由を教えてもらえますか?
他の人が指摘したように、理解可能な変数名を使用していないため、コードをデバッグすることはほとんど不可能です。
ダイクストラのアルゴリズムに関するwiki記事に従って、これらのラインに沿って(および他の数百万の方法で)実装できます。
nodes = ('A', 'B', 'C', 'D', 'E', 'F', 'G')
distances = {
'B': {'A': 5, 'D': 1, 'G': 2},
'A': {'B': 5, 'D': 3, 'E': 12, 'F' :5},
'D': {'B': 1, 'G': 1, 'E': 1, 'A': 3},
'G': {'B': 2, 'D': 1, 'C': 2},
'C': {'G': 2, 'E': 1, 'F': 16},
'E': {'A': 12, 'D': 1, 'C': 1, 'F': 2},
'F': {'A': 5, 'E': 2, 'C': 16}}
unvisited = {node: None for node in nodes} #using None as +inf
visited = {}
current = 'B'
currentDistance = 0
unvisited[current] = currentDistance
while True:
for neighbour, distance in distances[current].items():
if neighbour not in unvisited: continue
newDistance = currentDistance + distance
if unvisited[neighbour] is None or unvisited[neighbour] > newDistance:
unvisited[neighbour] = newDistance
visited[current] = currentDistance
del unvisited[current]
if not unvisited: break
candidates = [node for node in unvisited.items() if node[1]]
current, currentDistance = sorted(candidates, key = lambda x: x[1])[0]
print(visited)
このコードは必要以上に冗長です。あなたのコードと私のコードを比較して、いくつかの違いを見つけてください。
結果は次のとおりです。
{'E': 2, 'D': 1, 'G': 2, 'F': 4, 'A': 4, 'C': 3, 'B': 0}
初心者の読者にわかりやすくするために、より冗長な形式で記述しました。
from collections import defaultdict
def get_shortest_path(weighted_graph, start, end):
"""
Calculate the shortest path for a directed weighted graph.
Node can be virtually any hashable datatype.
:param start: starting node
:param end: ending node
:param weighted_graph: {"node1": {"node2": "weight", ...}, ...}
:return: ["START", ... nodes between ..., "END"] or None, if there is no
path
"""
# We always need to visit the start
nodes_to_visit = {start}
visited_nodes = set()
distance_from_start = defaultdict(lambda: float("inf"))
# Distance from start to start is 0
distance_from_start[start] = 0
tentative_parents = {}
while nodes_to_visit:
# The next node should be the one with the smallest weight
current = min(
[(distance_from_start[node], node) for node in nodes_to_visit]
)[1]
# The end was reached
if current == end:
break
nodes_to_visit.discard(current)
visited_nodes.add(current)
for neighbour, distance in weighted_graph[current]:
if neighbour in visited_nodes:
continue
neighbour_distance = distance_from_start[current] + distance
if neighbour_distance < distance_from_start[neighbour]:
distance_from_start[neighbour] = neighbour_distance
tentative_parents[neighbour] = current
nodes_to_visit.add(neighbour)
return _deconstruct_path(tentative_parents, end)
def _deconstruct_path(tentative_parents, end):
if end not in tentative_parents:
return None
cursor = end
path = []
while cursor:
path.append(cursor)
cursor = tentative_parents.get(cursor)
return list(reversed(path))
これは私の答えではありません-私の教授は私の試みよりもはるかに効率的にそれをしました。ここに彼のアプローチがあります。明らかに、反復タスクにヘルパー機能を使用しています
_def dijkstra(graph, source):
vertices, edges = graph
dist = dict()
previous = dict()
for vertex in vertices:
dist[vertex] = float("inf")
previous[vertex] = None
dist[source] = 0
Q = set(vertices)
while len(Q) > 0:
u = minimum_distance(dist, Q)
print('Currently considering', u, 'with a distance of', dist[u])
Q.remove(u)
if dist[u] == float('inf'):
break
n = get_neighbours(graph, u)
for vertex in n:
alt = dist[u] + dist_between(graph, u, vertex)
if alt < dist[vertex]:
dist[vertex] = alt
previous[vertex] = u
return previous
_
与えられたグラフ
({'A'、 'B'、 'C'、 'D'}、{( 'A'、 'B'、5)、( 'B'、 'A'、5)、( 'B'、 ' C '、10)、(' B '、' D '、6)、(' C '、' D '、2)、(' D '、' C '、2)})
コマンドprint(dijkstra(graph, 'A')
は次を生成します
現在、距離0のAを検討しています
現在、距離5のBを検討しています
現在、距離11のDを検討しています
現在、距離13のCを検討しています
id est:
{'C': 'D'、 'D': 'B'、 'A':なし、 'B': 'A'} =>ランダムな順序で
この実装は、配列とヒープdsのみを使用します。
import heapq as hq
import math
def dijkstra(G, s):
n = len(G)
visited = [False]*n
weights = [math.inf]*n
path = [None]*n
queue = []
weights[s] = 0
hq.heappush(queue, (0, s))
while len(queue) > 0:
g, u = hq.heappop(queue)
visited[u] = True
for v, w in G[u]:
if not visited[v]:
f = g + w
if f < weights[v]:
weights[v] = f
path[v] = u
hq.heappush(queue, (f, v))
return path, weights
G = [[(1, 6), (3, 7)],
[(2, 5), (3, 8), (4, -4)],
[(1, -2), (4, 7)],
[(2, -3), (4, 9)],
[(0, 2)]]
print(dijkstra(G, 0))
これが誰かの助けになることを願っています。少し遅れています。
Priority-queueを使用して、ダイクストラを実装します。それとは別に、私自身もmin-heapを実装しています。これがお役に立てば幸いです。
from collections import defaultdict
class MinPQ:
"""
each heap element is in form (key value, object handle), while heap
operations works based on comparing key value and object handle points to
the corresponding application object.
"""
def __init__(self, array=[]):
self._minheap = list(array)
self._length = len(array)
self._heapsize = 0
self._build_min_heap()
def _left(self, idx):
return 2*idx+1
def _right(self, idx):
return 2*idx+2
def _parent(self, idx):
return int((idx-1)/2)
def _min_heapify(self, idx):
left = self._left(idx)
right = self._right(idx)
min_idx = idx
if left <= self._heapsize-1 and self._minheap[left] < self._minheap[min_idx]:
min_idx = left
if right <= self._heapsize-1 and self._minheap[right] < self._minheap[min_idx]:
min_idx = right
if min_idx != idx:
self._minheap[idx], self._minheap[min_idx] = self._minheap[min_idx], self._minheap[idx]
self._min_heapify(min_idx)
def _build_min_heap(self):
self._heapsize = self._length
mid_id = int((self._heapsize)/2)-1
for i in range(mid_id, -1, -1):
self._min_heapify(i)
def decrease_key(self, idx, new_key):
while idx > 0 and new_key < self._minheap[self._parent(idx)]:
self._minheap[idx] = self._minheap[self._parent(idx)]
idx = self._parent(idx)
self._minheap[idx] = new_key
def extract_min(self):
if self._heapsize < 1:
raise IndexError
minimum = self._minheap[0]
self._minheap[0] = self._minheap[self._heapsize-1]
self._heapsize = self._heapsize - 1
self._min_heapify(0)
return minimum
def insert(self, item):
self._minheap.append(item)
self._heapsize = self._heapsize + 1
self.decrease_key(self._heapsize-1, item)
@property
def minimum(self):
return self._minheap[0]
def is_empty(self):
return self._heapsize == 0
def __str__(self):
return str(self._minheap)
__repr__ = __str__
def __len__(self):
return self._heapsize
class DiGraph:
def __init__(self, edges=None):
self.adj_list = defaultdict(list)
self.add_weighted_edges(edges)
@property
def nodes(self):
nodes = set()
nodes.update(self.adj_list.keys())
for node in self.adj_list.keys():
for neighbor, weight in self.adj_list[node]:
nodes.add(neighbor)
return list(nodes)
def add_weighted_edges(self, edges):
if edges is None:
return None
for Edge in edges:
self.add_weighted_Edge(edge)
def add_weighted_Edge(self, Edge):
node1, node2, weight = Edge
self.adj_list[node1].append((node2, weight))
def weight(self, tail, head):
for node, weight in self.adj_list[tail]:
if node == head:
return weight
return None
def relax(min_heapq, dist, graph, u, v):
if dist[v] > dist[u] + graph.weight(u, v):
dist[v] = dist[u] + graph.weight(u, v)
min_heapq.insert((dist[v], v))
def dijkstra(graph, start):
# initialize
dist = dict.fromkeys(graph.nodes, float('inf'))
dist[start] = 0
min_heapq = MinPQ()
min_heapq.insert((0, start))
while not min_heapq.is_empty():
distance, u = min_heapq.extract_min()
# we may add a node multiple time in priority queue, but we process it
# only once
if distance > dist[u]:
continue
for neighbor, weight in graph.adj_list[u]:
relax(min_heapq, dist, graph, u, neighbor)
return dist
import sys
import heapq
class Node:
def __init__(self, name):
self.name = name
self.visited = False
self.adjacenciesList = []
self.predecessor = None
self.mindistance = sys.maxsize
def __lt__(self, other):
return self.mindistance < other.mindistance
class Edge:
def __init__(self, weight, startvertex, endvertex):
self.weight = weight
self.startvertex = startvertex
self.endvertex = endvertex
def calculateshortestpath(vertexlist, startvertex):
q = []
startvertex.mindistance = 0
heapq.heappush(q, startvertex)
while q:
actualnode = heapq.heappop(q)
for Edge in actualnode.adjacenciesList:
tempdist = Edge.startvertex.mindistance + Edge.weight
if tempdist < Edge.endvertex.mindistance:
Edge.endvertex.mindistance = tempdist
Edge.endvertex.predecessor = Edge.startvertex
heapq.heappush(q,Edge.endvertex)
def getshortestpath(targetvertex):
print("The value of it's minimum distance is: ",targetvertex.mindistance)
node = targetvertex
while node:
print(node.name)
node = node.predecessor
node1 = Node("A");
node2 = Node("B");
node3 = Node("C");
node4 = Node("D");
node5 = Node("E");
node6 = Node("F");
node7 = Node("G");
node8 = Node("H");
Edge1 = Edge(5,node1,node2);
Edge2 = Edge(8,node1,node8);
Edge3 = Edge(9,node1,node5);
Edge4 = Edge(15,node2,node4);
Edge5 = Edge(12,node2,node3);
Edge6 = Edge(4,node2,node8);
Edge7 = Edge(7,node8,node3);
Edge8 = Edge(6,node8,node6);
Edge9 = Edge(5,node5,node8);
Edge10 = Edge(4,node5,node6);
Edge11 = Edge(20,node5,node7);
Edge12 = Edge(1,node6,node3);
Edge13 = Edge(13,node6,node7);
Edge14 = Edge(3,node3,node4);
Edge15 = Edge(11,node3,node7);
Edge16 = Edge(9,node4,node7);
node1.adjacenciesList.append(Edge1);
node1.adjacenciesList.append(Edge2);
node1.adjacenciesList.append(Edge3);
node2.adjacenciesList.append(Edge4);
node2.adjacenciesList.append(Edge5);
node2.adjacenciesList.append(Edge6);
node8.adjacenciesList.append(Edge7);
node8.adjacenciesList.append(Edge8);
node5.adjacenciesList.append(Edge9);
node5.adjacenciesList.append(Edge10);
node5.adjacenciesList.append(Edge11);
node6.adjacenciesList.append(Edge12);
node6.adjacenciesList.append(Edge13);
node3.adjacenciesList.append(Edge14);
node3.adjacenciesList.append(Edge15);
node4.adjacenciesList.append(Edge16);
vertexlist = (node1,node2,node3,node4,node5,node6,node7,node8)
calculateshortestpath(vertexlist,node1)
getshortestpath(node7)
抽出にブレークポイントを設定します。 Qからエントリを削除しますが、wからは削除しないことがわかります。それ以外はすべて辞書ですが、Q/wは最新の状態ではないペア配列です。これら2つの同期を維持するか、辞書に置き換える必要があります。特記事項:最終的に、アルゴリズムが機能した後、Q/wをエッジのリストに置き換え、優先キュー(heapqモジュール)で「抽出」関数を再コーディングすることができます。
さらに、wは常にソースに対して0の重みを持ち、他のすべてのノードに対して 'inf'を持っていることがわかります。候補距離を更新する重要なステップを完全にスキップしました。
したがって、基本的には、最短パスを選択するのではなく、遭遇する最初のパスを常に使用します。後でそのパスの実際の距離を計算するため、返される距離の配列には実際の値がありますが、それらは任意に選択されたものであり、最短になると考える理由はありません。
(誤って)次のノードを見つけたら、そのすべてのエッジを確認します。これは、次のノードの候補を更新する2番目の段落で前述した重要なステップであるはずです。代わりに、まったく異なることを行います。以前のすべてのソリューション(dijkstraを正しく実装する場合は正しいことが保証され、そのままにしておく必要があります)をすべてループしているように見え、source-> current-から2段階のソリューションを探します。 >任意。それらを見るための正しい意図は、前のパスから次の候補を次のノードに追加することでしたが、追加されないため、(前の最短パス)+(1ステップ)を見るのではなく、見るだけです文字通り2つのノードソリューションで。
したがって、基本的には、最短パスを見つけるために、ソースから可能なすべての2ノードパスをループしています。これは完全なエラーであり、dijkstraとは関係ありません。しかし、ほとんどの正しい最短パスが2ステップパスである小さな小さなグラフではほとんど機能します。
(ps:変数名について私は全員に同意します。変数が何を表すかを伝える詳細な名前を使用すると、はるかに良い結果が得られます。コードを分析する前に名前を変更する必要がありました。)
私のブログrebrained.comで、ウィキペディアの説明を以下の擬似コードに分解しました。
初期状態:
現在のノードループ:
import sys
def shortestpath(graph,start,end,visited=[],distances={},predecessors={}):
"""Find the shortest path btw start & end nodes in a graph"""
# detect if first time through, set current distance to zero
if not visited: distances[start]=0
# if we've found our end node, find the path to it, and return
if start==end:
path=[]
while end != None:
path.append(end)
end=predecessors.get(end,None)
return distances[start], path[::-1]
# process neighbors as per algorithm, keep track of predecessors
for neighbor in graph[start]:
if neighbor not in visited:
neighbordist = distances.get(neighbor,sys.maxint)
tentativedist = distances[start] + graph[start][neighbor]
if tentativedist < neighbordist:
distances[neighbor] = tentativedist
predecessors[neighbor]=start
# neighbors processed, now mark the current node as visited
visited.append(start)
# finds the closest unvisited node to the start
unvisiteds = dict((k, distances.get(k,sys.maxint)) for k in graph if k not in visited)
closestnode = min(unvisiteds, key=unvisiteds.get)
# now take the closest node and recurse, making it current
return shortestpath(graph,closestnode,end,visited,distances,predecessors)
if __name__ == "__main__":
graph = {'a': {'w': 14, 'x': 7, 'y': 9},
'b': {'w': 9, 'z': 6},
'w': {'a': 14, 'b': 9, 'y': 2},
'x': {'a': 7, 'y': 10, 'z': 15},
'y': {'a': 9, 'w': 2, 'x': 10, 'z': 11},
'z': {'b': 6, 'x': 15, 'y': 11}}
print shortestpath(graph,'a','a')
print shortestpath(graph,'a','b')
"""
Expected Result:
(0, ['a'])
(20, ['a', 'y', 'w', 'b'])
"""
これは、min-priority-queueを使用したダイクストラアルゴリズムの実装です。それがあなたを願っています。
from collections import defaultdict
from math import floor
class MinPQ:
"""
each heap element is in form (key value, object handle), while heap
operations works based on comparing key value and object handle points to
the corresponding application object.
"""
def __init__(self, array=[]):
self._minheap = list(array)
self._length = len(array)
self._heapsize = 0
self._build_min_heap()
def _left(self, idx):
return 2*idx+1
def _right(self, idx):
return 2*idx+2
def _parent(self, idx):
return floor((idx-1)/2)
def _min_heapify(self, idx):
left = self._left(idx)
right = self._right(idx)
min_idx = idx
if left <= self._heapsize-1 and self._minheap[left] < self._minheap[min_idx]:
min_idx = left
if right <= self._heapsize-1 and self._minheap[right] < self._minheap[min_idx]:
min_idx = right
if min_idx != idx:
self._minheap[idx], self._minheap[min_idx] = self._minheap[min_idx], self._minheap[idx]
self._min_heapify(min_idx)
def _build_min_heap(self):
self._heapsize = self._length
mid_id = int(self._heapsize-1)-1
for i in range(mid_id, -1, -1):
self._min_heapify(i)
def decrease_key(self, idx, new_key):
while idx > 0 and new_key < self._minheap[self._parent(idx)]:
self._minheap[idx] = self._minheap[self._parent(idx)]
idx = self._parent(idx)
self._minheap[idx] = new_key
def extract_min(self):
minimum = self._minheap[0]
self._minheap[0] = self._minheap[self._heapsize-1]
self._heapsize = self._heapsize - 1
self._min_heapify(0)
return minimum
def insert(self, item):
self._minheap.append(item)
self._heapsize = self._heapsize + 1
self.decrease_key(self._heapsize-1, item)
@property
def minimum(self):
return self._minheap[0]
def is_empty(self):
return self._heapsize == 0
def __str__(self):
return str(self._minheap)
__repr__ = __str__
def __len__(self):
return self._heapsize
class DiGraph:
def __init__(self, edges=None):
self.adj_list = defaultdict(list)
self.add_weighted_edges(edges)
@property
def nodes(self):
nodes = set()
nodes.update(self.adj_list.keys())
for node in self.adj_list.keys():
for neighbor, weight in self.adj_list[node]:
nodes.add(neighbor)
return list(nodes)
def add_weighted_edges(self, edges):
if edges is None:
return None
for Edge in edges:
self.add_weighted_Edge(edge)
def add_weighted_Edge(self, Edge):
node1, node2, weight = Edge
self.adj_list[node1].append((node2, weight))
def weight(self, tail, head):
for node, weight in self.adj_list[tail]:
if node == head:
return weight
return None
def relax(min_heapq, dist, graph, u, v):
if dist[v] > dist[u] + graph.weight(u, v):
dist[v] = dist[u] + graph.weight(u, v)
min_heapq.insert((dist[v], v))
def dijkstra(graph, start):
# initialize
dist = dict.fromkeys(graph.nodes, float('inf'))
dist[start] = 0
min_heapq = MinPQ()
min_heapq.insert((0, start))
while not min_heapq.is_empty():
distance, u = min_heapq.extract_min()
# we may add a node multiple time in priority queue, but we process it
# only once
if distance > dist[u]:
continue
for neighbor, weight in graph.adj_list[u]:
relax(min_heapq, dist, graph, u, neighbor)
return dist
if __name__ == "__main__":
edges = [('s', 't', 10), ('t', 'x', 1), ('s', 'y', 5), ('y', 't', 3), ('t', 'y', 2),
('y', 'x', 9), ('y', 'z', 2), ('z', 's', 7), ('x', 'z', 4), ('z', 'x', 6)]
digraph = DiGraph(edges)
res = dijkstra(digraph, 's')
print(res)