%load_ext lab_black
グラフの基礎
グラフ(graph)は点(node, vertex, point)集合 と枝(edge, arc, link)集合 から構成され, と記される. 点集合の要素を などの記号で表す. 枝集合の要素を と表す. 点間に複数の枝がない場合には,両端点 を決めれば一意に枝が 定まるので,枝を両端にある点の組として もしくは と表すことができる.
枝の両方の端にある点は,互いに隣接(adjacent)しているとよばれる. また,枝は両端の点に接続(incident)しているとよばれる. 点に接続する枝の本数を次数(degree)とよぶ.
枝に「向き」をつけたグラフを有向グラフ(directed graph, digraph) とよび,有向グラフの枝を有向枝(directed edge,arc, link)とよぶ. 一方,通常の(枝に向きをつけない)グラフであることを強調したいときには, グラフを無向グラフ(undirected graph)とよぶ. 点 から点 に向かう有向枝 に対して, を枝の尾(tail)もしくは始点, を枝の頭(head)もしくは終点とよぶ.また,点 を の後続点(successor), 点 を の先行点(predecessor)とよぶ.
パス(path)とは,点とそれに接続する枝が交互に並んだものである. 同じ点を通過しないパスを,特に単純パス(simple path)とよぶ. 閉路(circuit)とは,パスの最初の点(始点)と最後の点(終点)が同じ点であるグラフである. 同じ点を通過しない閉路を,特に単純閉路(cycle)とよぶ.
完全グラフ(complete graph)とは,すべての点間に枝があるグラフである. 完全2部グラフ(complete bipartite graph)とは,点集合を2つの部分集合に分割して, (各集合内の点同士の間には枝をはらず;これが2部グラフの条件である)異なる点集合に含まれるすべての点間に枝をはったグラフである.
ランダムグラフの生成
以下の関数では,グラフは点のリスト nodes と隣接点(の集合)のリスト adj として表現している.
ここで生成するグラフは,「メタヒューリスティクスの数理」(共立出版)で用いられたものであり,グラフ問題に対する様々なメタヒューリスティクスで用いられる.
- rnd_graph: 点数 n と点の発生確率 prob を与えるとランダムグラフの点リスト nodes と枝リスト edges を返す.
- rnd_adj: 点数 n と点の発生確率 prob を与えるとランダムグラフの点リスト nodesと隣接点のリスト adj を返す.
- rnd_adj_fast: rnd_adj関数の高速化版;大きなランダムグラフを生成する場合には,こちらを使う.
- adjacent: 点リスト nodes と枝リスト edges を与えると,隣接点のリスト adj を返す.
nodes, adj = rnd_adj_fast(10, 0.5)
print("nodes=", nodes)
print("adj=", adj)
グラフをnetworkXに変換する関数
networkXは,Python言語で使用可能なグラフ・ネットワークに対する標準パッケージである. networkXについては,http://networkx.github.io/ を参照されたい.
以下に,上の隣接リスト形式のグラフをnetworkXのグラフに変換するプログラムを示す.
G = to_nx_graph(nodes, adj)
print(G.edges())
networkXのグラフをPlotlyの図に変換する関数
Plotlyはオープンソースの描画パッケージである(https://plotly.com/python/). networkXのグラフをPlotlyの図オブジェクトに変換するプログラムを示す.
for v in G.nodes():
G.nodes[v]["color"] = random.randint(0, 3)
fig = to_plotly_fig(G)
plotly.offline.plot(fig);
ユーティリティー関数群
以下に本書で用いるグラフに対する様々なユーテリティ関数を示す.
- complement: 補グラフを生成する.
- shuffle: グラフの点と隣接リストをランダムにシャッフルする.
- read_gpp_graph: DIMACSのデータフォーマットのグラフ分割問題のグラフを読む.
- read_gpp_coords: DIMACSのデータフォーマットのグラフ分割問題の座標を読む.
- read_graph: DIMACSのデータフォーマットの最大クリーク問題のグラフを読む.
- read_compl_graph: : DIMACSのデータフォーマットの最大クリーク問題の補グラフを読む.
V = [1, 2, 3, 4, 5, 6, 7, 8, 9]
E = [
(1, 2),
(2, 3),
(4, 5),
(5, 6),
(7, 8),
(8, 9),
(1, 4),
(4, 7),
(2, 5),
(5, 8),
(3, 6),
(6, 9),
]
adj = make_adj(V, E)
(d, p) = bfs(V, adj, V[0])
for u in V:
print(f"d[{u}]={d[u]} p[{u}]={p[u]}")
def __dfs_visit(u, c):
c.color[u] = GRAY
c.d[u] = c.count
c.count = c.count + 1
for v in c.adj[u]:
if c.color[v] == WHITE:
c.p[v] = u
__dfs_visit(v, c)
c.color[u] = BLACK
c.topo.insert(0, u)
c.f[u] = c.count
c.count = c.count + 1
def dfs(V, adj):
class DfsContext:
pass
c = DfsContext()
c.adj = adj
c.color = {}
c.p = {}
c.d = {}
c.f = {}
c.topo = []
for u in V:
c.color[u] = WHITE
c.p[u] = NIL
c.count = 0
for u in V:
if c.color[u] == WHITE:
__dfs_visit(u, c)
return c
V = [1, 2, 3, 4, 5, 6, 7, 8, 9]
E = [
(1, 2),
(2, 3),
(4, 5),
(5, 6),
(7, 8),
(8, 9),
(1, 4),
(4, 7),
(2, 5),
(5, 8),
(3, 6),
(6, 9),
]
adj = make_adj(V, E)
c = dfs(V, adj)
for u in V:
print(f"d[{u}]={c.d[u]} f[{u}]={c.f[u]} p[{u}]={c.p[u]}")
print(c.topo)
V = [1, 2, 3, 4, 5, 6, 7, 8, 9]
E = [
(1, 4),
(4, 1),
(2, 3),
(2, 5),
(3, 6),
(5, 4),
(5, 9),
(6, 2),
(7, 8),
(8, 5),
(8, 7),
]
R = scc(V, E)
print(R)