同じところを 2 度通らない経路の数を数えるアルゴリズム

同じところを 2 度通らない経路の数を数えるアルゴリズム
川原 純∗
地図が与えられたとき、ある 2 地点を結ぶ経路の数を求めたいとします。ただし、ここでは、遠回りの経路
は認め、同じところを 2 度以上通る経路は認めないとします。高校数学では碁盤の目で左下から右上までの最
短経路の数を求める問題を習います。例えば、m × n の碁盤の目の場合、最短経路は (m + n)!/(m!n!) とな
ります。ところが、遠回りを許すとなると、問題は途端に難しくなります。そのような数学の公式は 2012 年
現在では見つかっていないのです。しかし、コンピュータで経路の数を効率良く数えるためのアルゴリズム
(計算手順)なら存在します。コンピュータ科学の世界で有名な Knuth (クヌース)先生の著書 The Art of
Computer Programming の VOLUME 4A, 254 ページで、そのアルゴリズムが紹介されています [3]
*1 。本
稿では、そのアルゴリズムの概要を解説します。
The Art of Computer Programming では、グラフ上の与えられた 2 点 s, t を結ぶパスをすべて列挙する
問題を考えています。アメリカのハワイ州とアラスカ州を除く 48 州を陸路で巡ることを考えたとき、カリ
フォルニア州をスタートとし、マサチューセッツ州をゴールとする巡り方をすべて列挙する高速なアルゴリズ
ムを与えよ、という演習問題が掲載されています。列挙さえできれば、数を数えるのは簡単ですので、以下で
はこの問題を考えます。
こ の ア ル ゴ リ ズ ム で は ZDD(Zero-Suppressed Binary Decision Diagram) [4] が 用 い ら れ て い
ま す の で 、ま ず は こ れ を 紹 介 し ま す 。ZDD と は 、集 合 族 を DAG に よ っ て 効 率 的 に 表 現 す る デ ー
タ 構 造 で す 。台 集 合 を {x1 , . . . , xn } と し 、変 数 に 順 序 x1 , . . . , xn が 定 め ら れ て い ま す 。例 え ば 、
{{x1 , x2 , x3 }, {x1 , x3 , x4 }, {x2 , x3 , x4 }} の集合族は図 1 のような ZDD で表されます。ZDD は 0 終端、1 終
端と呼ばれる 2 つの特殊なノードを持ちます。それ以外のノードは 0 枝、1 枝と呼ばれる 2 つの枝を持ちま
す。ノードにはラベル xi が振られています。ラベル xi のノードからラベル xj のノードへ枝が張られている
とき、i < j が成り立ちます。根ノードから 1 終端までのパス 1 本が 1 つの集合に対応し、1 枝を通ったノー
ドのラベルが集合の要素になります。ZDD はノードが共有できる場合は必ず共有を行います。また、1 枝の
先が 0 終端であるようなノードは存在してはいけません。
Knuth 先生のパス列挙アルゴリズム「Simpath」 (Simple path) を紹介します。辺に適当な順(例えば、s
からの幅優先順)をつけ、それらを e1 , . . . , em とします。解である s-t パスは辺の集合として表すことがで
きます。Simpath は、s-t パスを表現する辺集合をすべて集めたもの(=集合族)を考え、そのような集合族
を表現する ZDD を直接トップダウン的に構築するアルゴリズムであると言えます。ただし、ここで構築する
ZDD は、共有することが可能なノードでも共有されていないことがあるので、厳密には ZDD の定義に反し
ます。このような ZDD を PZDD(Pseudo ZDD)と呼ぶことにします。
最初にラベル e1 の振られたノードを用意し、e1 に 0 枝と 1 枝を接続させます(図 2)。0 枝と 1 枝の先には
∗
*1
科学技術振興機構 ERATO 湊離散構造処理系プロジェクト 研究員
この本でも、数学の公式は掲載されておりません。Knuth 先生は完全主義者であることで有名ですから、経路を求める数学の公式
が存在するなら、それを調べて紹介するはずです。もちろん、将来的に公式が発見される可能性はあります。
1
0枝
x1
1枝
x2
x1
x1
x2
x3
x3
x3
x3
x4
x4
x4
1
1
1
図1
x2
x2
x2
x3
x1
{x1, x2, x3} {x1, x3, x4} {x2, x3, x4}
0
1
0 終端
1 終端
{{x1 , x2 , x3 }, {x1 , x3 , x4 }, {x2 , x3 , x4 }} を表現する ZDD(左)。根ノードから 1 終端までの 1 本
のパスが 1 つの集合に対応している(右)。
それぞれラベル e2 が振られたノード(辺 e1 が解に含まれない状態と含まれる状態に対応)を接続させます。
e2 以降、em まで同じように「展開」します。em の 0 枝と 1 枝の先には 0 終端または 1 終端を接続させます。
終端には 1 つの辺集合が対応しますが、その辺集合が s-t パスを表現するなら 1 終端に、そうでないなら 0 終
端に接続させます。この方法によってノードが全く共有されていない PZDD が構築できます。
s
e2
e1
e1
e2
e3
e4
e3
e4
e2
e2
e3
e4
e1
e4
e3
e4
e4
e3
e4
e4
e2
e3
e4
0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0
e3
e4
1
t
図2
与えられたグラフと頂点 s, t(左)。二分決定木(中)
。最終的に出力したい (P)ZDD(右)
。
もちろんこのままではノードは共有されないので全く効率よくありません。そこで、各レベルで子の部分木
が等しくなるような 2 つのノードを共有することを考えます。ただし、子の部分木を作成する前に共有可能か
どうかを判定する必要があります。例えば、図 3 は辺 e1 , . . . , e10 まで処理済みで、次に辺 e11 を辺集合に加
えるかどうかの判定を行う 2 つの状態です。未処理の辺を処理するにあたり、本質的に必要な情報は、図の点
線の丸で囲った部分の各頂点が作りかけのパスの端であるかどうか、端であるならもう片方の端はどの頂点か
だけです。図ではどちらの状態も 6 番と 7 番の頂点、8 番と 1 番の頂点が作りかけのパスの両端になっている
ため、未処理の辺を処理してノード展開して得られる部分木は全く同じになります。各頂点について、作りか
けのパスの逆端を記憶しておきます。これを mate と呼びます。
図の点線の丸で囲った部分、すなわち、処理済みの辺に接続している頂点の集合から、未処理の辺が接続し
2
mate = 7
mate = 7
mate = 6
mate = 6
9
6
2
e1
s
e11
5
e10
1
t
s
7
8 11
frontier
e11
5
e1
12
3
4
図3
2
10
9
6
e10
1
10
12
t
3
4
mate = 1
7
8 11
frontier
mate = 1
mate と frontier。太線の辺が処理済みの辺で、辺集合に含まれる辺。点線の辺が処理済みの辺で、
辺集合に含まれない辺。細線の辺が未処理の辺。
ていない頂点を除去した頂点集合を frontier と呼ぶことにします*2 。frontier より「前」の頂点は未処理の部
分、
「後」の頂点は以降の処理において触れられることのない部分です。
各ノードの展開時に、frontier と mate を計算して各ノードに記憶させます(これは局所的に行えます)。
frontier の部分の mate が一致する 2 つのノードを等価であると呼ぶことにします。そして、構築中の PZDD
の各段において、ノード生成時に等価なノードが存在するなら、共有を行います。mate 配列を用いれば、s-t
パスが生成される見込みのない状態を判定してその時点で 0 終端に直結させることで、枝刈りを早い段階で行
うこともできます(詳しくは省略します)。
Simpath アルゴリズムで構築した PZDD からいつでも全 s-t パスを取り出すことができるので、PZDD は
全パスを圧縮して保持していると言うことができます。全パスから 1 つのパスを一様確率でランダムサンプリ
ングするのも簡単です。また、ZDD の代数演算(ZDD を集合族とみなしたときの集合演算)[5] を用いるこ
とで、様々な条件を指定したパスを得ることもできます。例えば、特定の辺を必ず通るような s-t パスのみを
ZDD の演算を施すことによって(ZDD の形で)効率良く得ることが可能です。従って、Simpath アルゴリ
ズムは単にパスを列挙するだけでなく、列挙した結果を後で利用するために索引を生成していると言うことが
できます。
PZDD を構築できれば、そこからパスの数を数えるのは簡単です。図 4 の各ノードの横に描かれている△
の中の数字は、その時点での経路の数を表します。この値を上から順に求めていきます。各ノードの△の値
は、上のノードの△の値の和になります。例えば、図 4 の c のノードは 2 つの枝が合流しており、上のノード
a,b の△の値はそれぞれ 1 と 1 ですので、c の△の値は 1 + 1 = 2 となります。最後に 1-終端の△の値がパス
の数となります。
Simpath アルゴリズムのソースコードは Knuth 先生の Web ページで公開されています*3 。“WEB”という
Knuth 先生が提唱する文芸的プログラミング言語で記述されたソースを「コンパイル」することで、C 言語の
ソースコードが出力されるので、それをさらにコンパイルすることで、実際に動くプログラムが得られます。
このソースコードは職人芸的に記述されており、解読は難関ですが、驚くほど高速に動作します。
Simpath アルゴリズムは、frontier に記憶させる mate のような情報と、枝刈りの条件判定法を問題に応じ
*2
アメリカ 48 州の経路を列挙する問題で、未処理の頂点集合が西部開拓時代の「frontier」が移動していく様であることから付けら
れたと思われる。Knuth 先生流の洒落の 1 つ。
*3
http://sunburn.stanford.edu/~uno/programs/simpath.w
3
s
e2
e1
1
1
e1
1
e2
e2
a
1 e3
e3
b
e3 1
c e4 2
e4
2
1
t
図4
パスの数の計算。
て変えることで、s-t パスだけでなく、様々な構造を列挙できます。例えば、サイクルの列挙、ハミルトンパ
ス・サイクルの列挙、マルチターミナルパスの列挙等は、条件判定の部分を少し変更するだけで可能になりま
す。さらに、全域木、カット、コンポーネント、マッチング等の列挙も “mate”として保持する情報を工夫す
ることで Simpath アルゴリズムの枠組みにあてはまります。frontier の前の部分と後ろの部分で依存関係が
それほど大きくなく、frontier の部分に frontier 以前の情報をうまく記憶させることができ、ノードの共有判
定が記憶させた情報によって行えるような構造であるならば、Simpath アルゴリズムによって列挙することが
可能です。
参考文献
[1] 今井 浩. ネットワーク信頼度計算の周辺 組合せ数え上げの新展開. 離散構造とアルゴリズム V (藤重悟
編), 近代科学社, pp. 1-50, 1998.
[2] 川原 純, 斎藤 寿樹, 鈴木 拡, 湊 真一, 吉仲 亮. ZDD によるパスの列挙. 京都大学数理解析研究所講究録,
VOL. 1744, pp. 35-41, 2011.
[3] D. Knuth. The Art of Computer Programming, Volume 4A.
[4] S. Minato. Zero-Suppressed BDDs for Set Manipulation in Combinatorial Problems. In DAC, pp.
272-277, 1993.
[5] S. Minato. Fast Factorization Method for Implicit Cube Set Representation. IEEE Trans. on
Computer-Aided Design of Integrated Circuits and Systems, VOL. 15, No. 4, pp. 377-384, 1996.
[6] K. Sekine, H. Imai and S. Tani. Computing the Tutte Polynomial of a Graph of Moderate Size.
Proc. ISAAC’95, VOL. 1004, pp. 224-233, 1995.
4