Pythonで理解する蟻本「2-5 最小全域木問題1(プリム法)」(p.100)
この記事は「プログラミングコンテストチャレンジブック第2版」(蟻本)の
「2-5 最小全域木問題1(プリム法)」(p.100)
のコードをPythonで書き直したものとなっています。
入力
入力例
7 9
0 1 2
0 4 10
1 2 1
1 3 3
1 5 7
3 5 1
3 6 5
4 5 5
5 6 8
蟻本に載っているコード(O(|V|^2))
INF = float('inf')
V, E = map(int,input().split()) # Vは頂点数、Eは辺数
cost = [[INF] * V for _ in range(V)] # cost[u][v]は辺e=(u,v)のコスト(存在しない場所はINF)
mincost = [INF] * V # 集合Xからの辺の最終コスト
used = [False] * V # 頂点iがXに含まれているか
for _ in range(E):
s, t, c = map(int,input().split())
cost[s][t] = c
cost[t][s] = c
def prim():
mincost[0] = 0
res = 0
while True:
v = -1
# Xに属さない頂点のうちXからの辺のコストが最小になる頂点を探す
for u in range(V):
if not used[u] and (v == -1 or mincost[u] < mincost[v]):
v = u
if v == -1:
break
used[v] = True # 頂点vをXに追加
res += mincost[v] # 辺のコストを加える
for u in range(V):
mincost[u] = min(mincost[u], cost[v][u])
return res
res = prim()
print(res)
実行結果
17
優先度付きキューを使った解法(O(|E| log |V|))
# 優先度付きキューをインポートする
import heapq
INF = float('inf')
V, E = map(int,input().split()) # Vは頂点数、Eは辺数
cost = [[INF] * V for _ in range(V)] # cost[u][v]は辺e=(u,v)のコスト(存在しない場所はINF)
used = [False] * V # 頂点iがXに含まれているか
for _ in range(E):
s, t, c = map(int,input().split())
cost[s][t] = c
cost[t][s] = c
def prim():
res = 0
q = [[0, 0]]
while True:
v = -1
MIN = INF
# Xに属さない頂点のうちXからの辺のコストが最小になる頂点を探す
while len(q) != 0:
c, u = heapq.heappop(q)
if not used[u] and (v == -1 or c < MIN):
v = u
MIN = c
break
if v == -1:
break
used[v] = True # 頂点vをXに追加
res += c # 辺のコストを加える
for u in range(V):
if cost[v][u] != INF:
heapq.heappush(q, [cost[v][u], u])
return res
res = prim()
print(res)
実行結果
17
Pythonで理解する蟻本「2-5 経路復元」(p.98)
この記事は「プログラミングコンテストチャレンジブック第2版」(蟻本)の
「2-5 経路復元」(p.98)
のコードをPythonで書き直したものとなっています。
入力例
頂点0→2→3→5→4→6が最短経路となる。
7 10
0 1 2
0 2 5
1 2 4
1 3 6
1 4 10
2 3 2
3 5 1
4 5 3
4 6 5
5 6 9
隣接リストを用いたコード(O(|V|^2))
INF = float('inf')
prev = []
V, E = map(int,input().split()) # Vは頂点数、Eは辺数
cost = [[INF] * V for _ in range(V)] # cost[u][v]は辺e=(u,v)のコスト(存在しない場所はINF)
for _ in range(E):
s, t, c = map(int,input().split())
cost[s][t] = c
cost[t][s] = c
d = [INF for _ in range(V)] # 最短距離
used = [False] * V # すでに使われたかのフラグ
prev = [-1] * V # 最短路の直前の頂点
# 始点sから各頂点への最短距離を求める
def dijkstra(s):
d[s] = 0
while True:
v = -1
for u in range(V):
if not used[u] and (v == -1 or d[u] < d[v]):
v = u
if v == -1:
break
used[v] = True
for u in range(V):
if d[u] > d[v] + cost[v][u]:
d[u] = d[v] + cost[v][u]
prev[u] = v
# 頂点tへの最短路
def get_path(t):
path = []
while t != -1:
path.append(t) # tがsになるまでprev[t]を辿っていく
t = prev[t]
# このままだとt->sの順になっているので逆順にする
path.reverse()
return path
dijkstra(0) # 頂点0を始点とする
path = get_path(6) # 頂点0から頂点6への最短経路
print(path)
実行結果
[0, 2, 3, 5, 4, 6]
Pythonで理解する蟻本「2-5 全点対最短路問題(ワーシャル-フロイド法)」(p.98)
この記事は「プログラミングコンテストチャレンジブック第2版」(蟻本)の
「2-5 全点対最短路問題(ワーシャル-フロイド法)」(p.98)
のコードをPythonで書き直したものとなっています。
入力
蟻本には負の閉路がないグラフだけが入力例として載っていたので、負の閉路があるグラフも入力例として載せておきます。
また、蟻本に載っているグラフは無向グラフですが、このブログで追加した負の閉路があるグラフの入力例では有向グラフを使っています。
そのため、この記事では入力例は有向グラフに統一してあります。
入力例1
上記のコードは実際に実行すると のように一行で表示されます。 しかし、この形式はとても見にくいため下の実行例では というように改行しています。 実際の実行結果とは異なりますが、リストの中身自体は同じなので問題ありません。 これは上の実行例でも確かに成り立っています。
7 20
0 1 2
1 0 2
0 2 5
2 0 5
1 2 4
2 1 4
1 3 6
3 1 6
1 4 10
4 1 10
2 3 2
3 2 2
3 5 1
5 3 1
4 5 3
5 4 3
4 6 5
6 4 5
5 6 9
6 5 9
入力例2
6 7
0 1 2
0 3 4
1 2 3
2 3 5
2 5 2
4 2 -4
5 4 1
コード
INF = float('inf')
V, E = map(int,input().split()) # Vは頂点数、Eは辺数
d = [[INF] * V for _ in range(V)] # d[u][v]は辺e=(u,v)のコスト(存在しない場合はINF、ただしd[i][i]=0とする)
for i in range(V):
d[i][i] = 0
for _ in range(E):
f, t, c = map(int,input().split())
d[f][t] = c
def warshall_floyd():
for k in range(V):
for i in range(V):
for j in range(V):
d[i][j] = min(d[i][j], d[i][k] + d[k][j])
warshall_floyd()
print(d)
実行結果
[[0, 1, 2], [3, 4, 5], [6, 7, 8]]
[[0, 1, 2],
[3, 4, 5],
[6, 7, 8]]
入力例1の場合
[[0, 2, 5, 7, 11, 8, 16],
[2, 0, 4, 6, 10, 7, 15],
[5, 4, 0, 2, 6, 3, 11],
[7, 6, 2, 0, 4, 1, 9],
[11, 10, 6, 4, 0, 3, 5],
[8, 7, 3, 1, 3, 0, 8],
[16, 15, 11, 9, 5, 8, 0]]
入力例2の場合
[[0, 2, 4, 4, 8, 6],
[inf, 0, 2, 7, 6, 4],
[inf, inf, -1, 4, 3, 1],
[inf, inf, inf, 0, inf, inf],
[inf, inf, -5, 0, -1, -3],
[inf, inf, -4, 1, 0, -2]]
頂点iから頂点jに到達できない場合はd[i][j] = infとなり、負の閉路が存在する場合はd[i][i]が負となる頂点iが存在します。
Pythonで理解する蟻本「2-5 単一始点最短路問題2(ダイクストラ法)」(p.96)
この記事は「プログラミングコンテストチャレンジブック第2版」(蟻本)の
「2-5 単一始点最短路問題2(ダイクストラ法)」(p.96)
のコードをPythonで書き直したものとなっています。
入力例
7 10
0 1 2
0 2 5
1 2 4
1 3 6
1 4 10
2 3 2
3 5 1
4 5 3
4 6 5
5 6 9
隣接リストを用いたコード(O(|V|^2))
INF = float('inf')
V, E = map(int,input().split()) # Vは頂点数、Eは辺数
cost = [[INF] * V for _ in range(V)] # cost[u][v]は辺e=(u,v)のコスト(存在しない場所はINF)
for _ in range(E):
s, t, c = map(int,input().split())
cost[s][t] = c
cost[t][s] = c
d = [INF for _ in range(V)] # 最短距離
used = [False] * V # すでに使われたかのフラグ
# 始点sからの最短距離のリストを返す
def dijkstra(s):
d[s] = 0
while True:
v = -1
# まだ使われていない頂点のうち距離が最小のものを探す
for u in range(V):
if not used[u] and (v == -1 or d[u] < d[v]):
v = u
if v == -1:
break
used[v] = True
for u in range(V):
d[u] = min(d[u], d[v] + cost[v][u])
dijkstra(0) # 頂点0を始点とする
print(d)
実行結果
[0, 2, 5, 7, 11, 8, 16]
優先度付きキューを使った解法(O(|E| log |V|))
# 優先度付きキューをインポートする
import heapq
INF = float('inf')
V, E = map(int,input().split()) # Vは頂点数、Eは辺数
G = [[] for _ in range(V)]
for _ in range(E):
s, t, c = map(int,input().split())
G[s].append([t, c])
G[t].append([s, c])
d = [INF for _ in range(V)]
def dijkstra(s):
d[s] = 0
que = []
heapq.heappush(que, (0, s))
while len(que) != 0:
p = heapq.heappop(que)
v = p[1]
if d[v] < p[0]:
continue
for i in range(len(G[v])):
e = G[v][i]
if d[e[0]] > d[v] + e[1]:
d[e[0]] = d[v] + e[1]
heapq.heappush(que, (d[e[0]], e[0]))
dijkstra(0) # 頂点0を始点とする
print(d)
実行結果
[0, 2, 5, 7, 11, 8, 16]
Pythonで理解する蟻本「2-5 単一始点最短路問題1(ベルマンフォード法)」(p.95)
この記事は「プログラミングコンテストチャレンジブック第2版」(蟻本)の
「2-5 単一始点最短路問題1(ベルマンフォード法)」(p.95)
のコードをPythonで書き直したものとなっています。
入力
蟻本には負の閉路がないグラフだけが入力例として載っていたので、負の閉路があるグラフも入力例として載せておきます。
また、蟻本に載っているグラフは無向グラフですが、このブログで追加した負の閉路があるグラフの入力例では有向グラフを使っています。
そのため、この記事では入力例は有向グラフに統一してあります。
入力例1
負の閉路があるため無限ループに入ります。 蟻本には載っていませんでしたが、上記の二つを合わせたものを載せておきます。 このコードでは、負閉路を利用することで最短距離が無限に小さくなるような頂点との距離を-INFと出力します。 このコードも計算量はになります。
7 20
0 1 2
1 0 2
0 2 5
2 0 5
1 2 4
2 1 4
1 3 6
3 1 6
1 4 10
4 1 10
2 3 2
3 2 2
3 5 1
5 3 1
4 5 3
5 4 3
4 6 5
6 4 5
5 6 9
6 5 9
入力例2
6 7
0 1 2
0 3 4
1 2 3
2 3 5
2 5 2
4 2 -4
5 4 1
負の閉路が無い場合のコード
INF = float('inf')
V, E = map(int,input().split()) # Vは頂点数、Eは辺数
es = [] # 辺
for _ in range(E):
# 頂点fromから頂点toへのコストcostの辺
f, t, c = map(int,input().split())
es.append([f, t, c])
d = [INF] * V # 最短距離
# s番目の頂点から各頂点への最短経路を求める
def shortest_path(s):
d[s] = 0
while True:
update = False
for i in range(E):
e = es[i]
if d[e[0]] != INF and d[e[1]] > d[e[0]] + e[2]:
d[e[1]] = d[e[0]] + e[2]
update = True
if not update:
break
shortest_path(0) # 頂点0を始点とする
print(d)
実行結果
入力例1の場合
[0, 2, 5, 7, 11, 8, 16]
入力例2の場合
負閉路を検出するコード
INF = float('inf')
V, E = map(int,input().split()) # Vは頂点数、Eは辺数
es = [] # 辺
for _ in range(E):
# 頂点fromから頂点toへのコストcostの辺
f, t, c = map(int,input().split())
es.append([f, t, c])
# trueなら負の閉路が存在する
def find_negative_loop():
d = [0] * V
for i in range(V):
for j in range(E):
e = es[j]
if d[e[1]] > d[e[0]] + e[2]:
d[e[1]] = d[e[0]] + e[2]
# n回目にも更新があるなら負の経路が存在する
if i == V-1:
return True
return False
find_negative_loop()
実行結果
入力例1の場合
True
入力例2の場合
False
最短距離を求め負閉路があれば検出するコード
INF = float('inf')
V, E = map(int,input().split()) # Vは頂点数、Eは辺数
es = [] # 辺
for _ in range(E):
# 頂点fromから頂点toへのコストcostの辺
f, t, c = map(int,input().split())
es.append([f, t, c])
def bellmanford(s):
d = [INF] * V
d[s] = 0
for i in range(V):
for j in range(E):
e = es[j]
if d[e[1]] > d[e[0]] + e[2]:
d[e[1]] = d[e[0]] + e[2]
# n回目にも更新があるなら負の経路が存在する
if i == V-1:
d[e[1]] = -INF
while True:
update = False
for i in range(len(es)):
e = es[i]
if d[e[1]] != -INF and d[e[0]] == -INF:
d[e[1]] = -INF
update = True
if not update:
break
return d
D = bellmanford(0) # 頂点0を始点とする
print(D)
実行結果
入力例1の場合
[0, 2, 5, 7, 11, 8, 16]
入力例2の場合
[0, 2, -inf, -inf, -inf, -inf]
Pythonで理解する蟻本「2-5 二部グラフ判定」(p.93)
この記事は「プログラミングコンテストチャレンジブック第2版」(蟻本)の
「2-5 二部グラフ判定」(p.93)
のコードをPythonで書き直したものとなっています。
入力
蟻本の入力例ではグラフが書かれていただけだったので、この記事ではAtCoderでよくあるような入力形式で入力例を書き直しておきました。
そのため、グラフの入力を受け取る部分は蟻本に載っているコードとは異なります。
この入力の受け取り方は『Pythonで理解する蟻本「2-5 隣接リスト」(p.91)』に載っているのでそちらを参考にしてください。
入力例
入力例1
3
3
0 1
0 2
1 2
入力例1
4
4
0 1
0 3
1 2
2 3
解答
# DFSを使うため再帰限界に引っかからないようにしておく
import sys
sys.setrecursionlimit(4100000)
MAX_V = 1000
# 入力
V = int(input()) # 頂点数
E = int(input()) # 辺数
G = [[] for _ in range(MAX_V)] # グラフ
for i in range(E):
# sからtへの辺を張る
s, t = map(int,input().split())
G[s].append(t)
G[t].append(s)
color = [0] * MAX_V # 頂点iの色(1 or -1)
# 頂点を1と-1で塗っていく
def dfs(v, c):
color[v] = c # 頂点vをcで塗る
for i in range(len(G[v])):
# 隣接している頂点が同じ色ならFalse
if color[G[v][i]] == c:
return False
# 隣接している頂点がまだ塗られていないなら-cで塗る
if color[G[v][i]] == 0 and not dfs(G[v][i], -c):
return False
# すべての頂点を塗れたらTrue
return True
for i in range(V):
if color[i] == 0:
# まだ頂点iが塗られていなければ1で塗る
if not dfs(i, 1):
print("No")
sys.exit()
print("Yes")
Pythonで理解する蟻本「2-5 隣接リスト」(p.91)
この記事は「プログラミングコンテストチャレンジブック第2版」(蟻本)の
「2-5 隣接リスト」(p.91)
のコードをPythonで書き直したものとなっています。
隣接リストのコード
例1
MAX_V = 10 ** 8 G = [0] * MAX_V ''' 辺に属性がある場合 edge = [to, cost] G = [edge for _ in range(MAX_V)] ''' V = int(input()) E = int(input()) for i in range(E): # sからtへの辺を張る s = int(input()) t = int(input()) G[s].append(t) # G[t].append(s) 無向グラフの場合はさらにtにsへの辺を張る ''' グラフの操作 '''
例2のコードは翻訳できませんでした…
翻訳できる方がいらっしゃったら僕のツイッターにコードを送って頂けると助かりますm(_ _)m