【幅優先探索】木構造(グラフ)における頂点間距離を求める【Python】

アルゴリズム
スポンサーリンク

木構造を扱う際、頂点間の距離や根からの距離を求めたり、木の直径を求めたりする場面があります。本記事では幅優先探索(BFS)の考え方を図で直感的にイメージした後、グラフ(木構造)の頂点間の距離に関する具体的な問題を解説しています。

スポンサーリンク

幅優先探索のイメージをつかむ

  • dequeに調べるべき頂点(ノード)を格納する。
  • dequeの先頭nowをdeque.popleft()で取り出す。popの取り出しにより、dequeの2番目の要素が先頭に繰り上がる。
  • nowに隣接する頂点群g[now]の要素nxtをすべて訪問し、訪問完了後dequeの末尾に加えていく
  • nowに関する調査は終わり。
  • dequeの先頭(さっきは2番目の要素だったもの)を新たなnowとし、nowに隣接する頂点群g[now]の要素nxtをすべて訪問し、訪問完了後dequeの末尾に加えていく

言葉だけでは分かりにくいので、図を使って考えてみます。

まず、根をnowとしてスタートし、根に隣接する頂点をg[now]=nextとします。

次に、next[0]を訪問し、根からの距離=1をリストdistに格納します。現在のnow(next[0])に隣接する未訪問の頂点が存在しないので、これ以上深くは進むことができません。

次にnext[1]を訪問します。根からの距離=1をリストdistに格納します。現在のnow(next[1])に隣接する未訪問の頂点が2つ存在するので、それら2つは訪問予定となります。

以後、上で行った操作を繰り返していけば、「根からの距離」=深さの小さい順に訪問し、根からの距離を求めることができます。

以上、幅優先探索で各頂点を訪問していく流れです。

スポンサーリンク

1. 頂点間の距離を求める(根からの距離を求める)

木についての情報と頂点番号 A と整数 X が与えられるので、A から距離が X の全ての頂点の番号を昇順で出力してください。

引用元:https://paiza.jp/works/mondai/tree_primer/tree_primer__path

とある木構造についての情報(隣接する頂点番号の組a_i, b_i)と、根の頂点番号Aが与えられます。この時、頂点(根)Aからの距離がXである頂点をすべて出力せよという問題です。

ポイント

  • グラフ(木構造)gを2次元リストで表現する。
  • 各頂点の根からの距離を格納するリストdistを作成する。
  • BFSで各頂点を訪問しながら根からの距離を求め、結果をdistに格納していく。

コード例

# 木構造において、根からの距離を求めたい
# 全ての頂点について A からの距離を求めるには A から幅優先探索を行えば良い
from collections import deque #幅優先探索のためのキュー

N, A, X = map(int, input().split())
# 木を表現する
g = [[] for _ in range(N)]
for _ in range(N - 1):
    a, b = map(int, input().split())
    a -= 1
    b -= 1
    g[a].append(b)
    g[b].append(a)

q = deque()
q.append(A - 1)
# 各点の深さを格納するためのリスト(メモ: 全頂点が1列に並んでいたとしても絶対に超えない深さにする)
dist = [101] * N
# 頂点の深さは当然0
dist[A - 1] = 0
def BFS():
    while q:
        # 先頭を取り出してnowとする
        now = q.popleft()
        # nowと隣接する要素をすべて調べる
        for nxt in g[now]:
            # 未訪問なら距離を記録する
            if dist[nxt] == 101:
                dist[nxt] = dist[now] + 1
                q.append(nxt)
BFS()
for i in range(N):
    if dist[i] == X:
        print(i + 1)
スポンサーリンク

2. 頂点間の経路を求める

木についての情報と頂点番号 A, B が与えられるので、A から B までの経路上の頂点を通る順に出力してください。なお、木においては異なる 2 頂点間を結ぶ経路は 1 通りに定まることが保証されています。

引用元:https://paiza.jp/works/mondai/tree_primer/tree_primer__path

頂点Aを根とし、頂点Bまでの経路を求めるに等しいと解釈できます。

ポイント

  • グラフ(木構造)gを2次元リストで表現する。
  • 各頂点に対し、1階層上(頂点Aが一番上とする)の頂点、すなわち親となる頂点のリストupperを作成する。
  • BFSで各頂点を訪問しながら、頂点nxtの1階層上のnowをリストupperに格納していく。
  • upperの要素は、BからAにさかのぼる順に格納されているので、最後は逆順で出力。

コード例

from collections import deque #幅優先探索のためのキュー

N, A, B = map(int, input().split())

# 木を表現する
g = [[] for _ in range(N)]
for _ in range(N - 1):
    a, b = map(int, input().split())
    a -= 1
    b -= 1
    g[a].append(b)
    g[b].append(a)

q = deque()
q.append(A - 1)
upper = [-1] * N
# 頂点Aは根であることを, -2で表現する
upper[A - 1] = -2

def BFS():
    while q:
        now = q.popleft()
        for nxt in g[now]:
            if upper[nxt] == -1:
                upper[nxt] = now
                q.append(nxt)
BFS()
                
ans = []
now = B - 1

# 頂点
while upper[now] != -2:
    ans.append(now)
    now = upper[now]
# 最後に頂点をansに追加する
ans.append(A-1)
# ansにはB=>Aとたどる経路を格納したので, 逆順に出力する
for i in ans[::-1]:
    print(i + 1)

3. 木の直径を求める

ある木における、最も遠い 2 頂点間の距離をその木の直径といいます。
木 T についての情報が与えられるので、T の直径の値を求めてください。

引用元:https://paiza.jp/works/mondai/tree_primer/tree_primer__diameter_of_tree

ポイント

  • 適当な頂点sから最も遠い頂点vを求める(BFS)。
    ※頂点vはグラフの性質(少々複雑な背理法で証明可能)により、木の直径を成す端点の1つになる。直感的な証明方法はないので、ここでは事実として扱う。
  • よって、頂点vを根とし、vから最も遠い頂点uまでの深さを求めればよい(BFS, 問題の1. とほぼ同じ解き方)。

コード例

from collections import deque

# 頂点sからスタートする幅優先探索
def BFS(s):
    dist = [-1] * N
    dist[s] = 0
    q = deque()
    q.append(s)
    while q:
        now = q.popleft()
        for nxt in g[now]:
            if dist[nxt] == -1:
                dist[nxt] = dist[now] + 1
                q.append(nxt)

    return dist

N = int(input())

g = [[] for _ in range(N)]
for _ in range(N - 1):
    a, b = map(int, input().split())
    a -= 1
    b -= 1
    g[a].append(b)
    g[b].append(a)

# 頂点1(リスト内では0)からスタート
dist_from_zero = BFS(0)
# 頂点1からの最長距離を求める
max_dist = max(dist_from_zero)
for i, j in enumerate(dist_from_zero):
    # 頂点1から最も遠い頂点jを探す
    if j == max_dist:
        diameter = max(BFS(i))
        print(diameter)
        break

コメント