問題: 関数が与えられる。によって定まるfunctional graph上の閉路の情報をで得たい。具体的には、次の配列を作りたい:
- 閉路上の頂点でに最も近いもの。より形式的には、が閉路に含まれるような最小の非負整数に関する。
- が閉路に含まれるならその閉路の長さ。そうでないならからまでの距離。
以下はグラフと求めたい値の例。頂点番号 (cycleの値, lengthの値)
という表記になっている。
アルゴリズム
長さの配列を初期化する。はで埋め、はで埋める。
頂点について、それぞれ未訪問()であればに関するイテレーションを開始する。
1回のイテレーションにおいては、を始点にしてを繰り返してグラフを辿っていく。この際、からまでの距離を保持して、と記録していく。以前に訪れた頂点()を再訪したところでストップするが、このとき、がこのイテレーションで初めて訪問した頂点である()か、以前のイテレーションで訪問した頂点かで必要な処理が異なる。
がこのイテレーションで初めて訪問した頂点であれば新しい閉路を発見したことになるので、閉路の長さ()に従って、閉路をもう一周しながらとを埋める。さらに、始点から閉路に達するまでの頂点についてもとを埋める。
が以前のイテレーションで訪問した頂点の場合、始点から辿ってをで埋めていく。については、が閉路中の頂点()であればを採用し、が閉路外の頂点であればを採用して始点から埋めていく。
どの頂点についても高々2回しかを適用しないので。
以下はPythonの実装例:
def get_cycle_info(f: list[int]) -> tuple[list[int], list[int]]:
"""{0, 1, ..., N-1}上の写像fを入力として2つのリストcycle, lengthを返す
cycle[v] := 閉路に含まれるような最初のf(f(...f(v)...))
length[v] := vが閉路上の頂点の場合は閉路の長さ、そうでない場合はvから閉路までの距離
"""
n = len(f)
tmp = [-1] * n
cycle = [0] * n
length = [0] * n
for start in range(n):
v = start
dist = 0
while tmp[v] == -1:
tmp[v] = dist
v = f[v]
dist += 1
if length[v] == 0:
u = start
path_length = tmp[v]
for i in range(path_length):
cycle[u] = v
length[u] = path_length - i
u = f[u]
cycle_length = dist - tmp[v]
for _ in range(cycle_length):
cycle[v] = v
length[v] = cycle_length
v = f[v]
else:
u = start
path_length = dist if v == cycle[v] else dist + length[v]
for i in range(dist):
cycle[u] = cycle[v]
length[u] = path_length - i
u = f[u]
return cycle, length
cycle, length = get_cycle_info([1, 2, 3, 4, 5, 6, 3, 8, 1, 10, 11, 10, 6, 12, 14])
assert cycle == [3, 3, 3, 3, 4, 5, 6, 3, 3, 10, 10, 11, 6, 6, 14]
assert length == [3, 2, 1, 4, 4, 4, 4, 4, 3, 1, 2, 2, 1, 2, 1]
cycle, length = get_cycle_info([1, 2, 2])
assert cycle == [2, 2, 2]
assert length == [2, 1, 1]
cycle, length = get_cycle_info([])
assert cycle == []
assert length == []
たぶん基本的な問題だけど書いてある文献が見つからなかったのでメモ。