酒居敬一(sakai.keiichi@kochi-tech.ac.jp) アルゴリズムとデータ構造 2012年7月12日 酒居敬一(sakai.keiichi@kochi-tech.ac.jp) http://www.info.kochi-tech.ac.jp/k1sakai/Lecture/ALG/2012/index.html
各種の連結性の判定 (249ページ) 連結の強さを表現する概念 無向グラフ 二重連結 二重連結成分 有向グラフ 強連結 強連結成分
二重連結 F A D G C B E H I (250ページ) 図4.4.1 二重連結グラフ グラフから1つの頂点と その頂点を端点とする 図4.4.1 二重連結グラフ G グラフから1つの頂点と その頂点を端点とする すべての辺を除いて得られる グラフを考えたとき、どの頂点を 除いた場合でもグラフが連結 グラフであるとき、元のグラフは 二重連結であるという。 C B E 図4.4.2 関節点と二重連結成分 H I 二重連結成分とは、二重 連結な部分グラフのこと。 取り除くと非連結になる 頂点を関節点という。
関節点検出のアルゴリズム 前提条件 場合分け 関節点が検出されなければ、二重連結。 (251ページ) 前提条件 関節点が検出されなければ、二重連結。 深さ優先探索の結果の木を仮定して、 子孫・祖先・部分木という言葉を使う。 場合分け 頂点が木の根の場合 子が2個以上ならその頂点は関節点である。 頂点が木の根ではない場合(次のページ) 着目する3頂点の位置で2つに場合分けする。 いずれもAB間・BC間・CA間の道について考える。
BはAの祖先、CはAの子孫、 という位置関係の場合。 もしAが関節点ならば、 以下の2つは同じことを言っている BC間の道が必ずAを通ること (図 4.4.3 a) A B C もしAが関節点ならば、 以下の2つは同じことを言っている BC間の道が必ずAを通ること Cを含む部分木の中にAの祖先への 逆辺がないこと 図のように逆辺 (点線で表示)があれば、 Aを経由せずにBC間を移動できるので、 この場合はAは関節点ではない。
BはAの子孫、CもAの子孫、 という位置関係の場合。 (図 4.4.3 b) A B C 図のように、BとCの双方が、Aの祖先と Aを経由せずに連絡できれば、Aは 関節点ではない。この場合、Aを経由 せずにBとCが連絡できる。 BとCがAを通らないと連絡できない のは、どちらかの部分木にAより上の 祖先を指す逆辺がないからであり、 Aが関節点になっている。
根の子が2個以上(順序数が2より大きい) ときは、根が関節点。 public class Biconnected<E> { private int sequence; private int visit(Node<E> node){ node.setSequence(++this.sequence); int min = this.sequence; for(Node<E> neighbor: node.getEdges()){ if(neighbor.getSequence() == 0){ int m = visit(neighbor); min = Math.min(min, m); boolean artic; if (node.getSequence() == 1) { artic = (neighbor.getSequence() != 2); } else { artic = (m >= node.getSequence()); } if(artic) System.out.println("頂点" + node.getValue() + "は関節点"); } else if (neighbor.getSequence() < min){ min = neighbor.getSequence(); return min; public void search(Node<E> start){ this.sequence = 0; visit(start); 隣接する未訪問の頂点に関しては、 深さ優先探索なので探索する。 その結果、訪問順が付けられる。 根の子が2個以上(順序数が2より大きい) ときは、根が関節点。 自分(node)の子から、祖先への逆辺 がなければ、自分は関節点。 隣接する訪問済みの頂点に関しては、 より祖先へ向かう辺(逆辺含む)の先の 頂点の順序数を保持する。 深く探索していく過程で、より祖先寄りの祖先への逆辺を 調べながら進んでいる。
H I A B C E D F G 図4.4.2 頂点Dは関節点 頂点Eは関節点 頂点Cは関節点 public static void main(String[] args) { System.out.println("図4.4.2"); for(Node<Character> node: test_data2){ node.reset(); } nodeA.connect(nodeC); nodeA.connect(nodeD); nodeB.connect(nodeC); nodeB.connect(nodeD); nodeC.connect(nodeE); nodeD.connect(nodeF); nodeD.connect(nodeG); nodeF.connect(nodeG); nodeE.connect(nodeH); nodeE.connect(nodeI); new Biconnected<Character>().search(nodeA); 図4.4.2 頂点Dは関節点 頂点Eは関節点 頂点Cは関節点
強連結 (255ページ) 部分グラフのうち、強連結であって、 しかもそれ以上頂点を追加すると 強連結でなくなるものを強連結成分という。 図4.4.5 強連結グラフ 有向グラフの各頂点から任意の 頂点へ至る道が存在するとき、 この有向グラフを強連結であるという。 図4.4.6 強連結成分への分割
強連結成分への分割アルゴリズム 有向グラフの深さ優先探索の応用である。 下降辺は強連結性に貢献しない。 (256ページ) 有向グラフの深さ優先探索の応用である。 下降辺は強連結性に貢献しない。 下降辺の両端の頂点について、祖先から子孫へ 木の辺から成る道がすでに存在する。 上昇辺の両端の頂点は同じ強連結成分に属す。 木の辺と上昇辺で閉路を作る。 木の辺で下降し、上昇辺で上昇して元に戻れる。 交差辺は、単純ではない。 強連結成分に属す・属さないの2種類がある。
図4.4.7 深さ優先探索の木と強連結成分の関係 (木の辺だけを見たときは木) 深さ優先探索の木の上で強連結成分を表す。 実際は下降辺・上昇辺・交差辺も存在してて、 それらとともに強連結成分を作っている。 各強連結成分に最初に訪問した頂点 =その部分木の根。 部分木の根からその子孫に到達可能。 部分木が強連結成分なら、 木の辺を0回以上通った後で 上昇辺か交差辺を通って根に戻れる。 深さ優先探索の木の葉から、上昇辺 や交差辺を通ってその葉の祖先に たどり着くことができ、そういう祖先の うちで最も根に近いものを求める。 教科書のlowlink変数 強連結成分を見つけたら、 この辺を木からはずす。 図4.4.7 深さ優先探索の木と強連結成分の関係 (木の辺だけを見たときは木)
そこからたどれる最も根に 近い頂点の訪問順を更新する。 訪問済みなので木の辺じゃない。 public class StrongComponent<E> { private int sequence; private Stack<Node<E>> stack = new Stack<Node<E>>(); private int visit(Node<E> node){ node.setSequence(++this.sequence); int min = this.sequence; stack.push(node); for(Node<E> neighbor: node.getEdges()){ int m; if(neighbor.getSequence() == 0) m = visit(neighbor); else m = neighbor.getSequence(); min = Math.min(min, m); } if(min == node.getSequence()){ Node<E> component; do { component = stack.pop(); System.out.print(component.getValue() + " "); component.setSequence(Integer.MAX_VALUE); } while (component != node); System.out.println(); return min; public void search(Collection<Node<E>> graph){ this.sequence = 0; this.stack.clear(); for(Node<E> node: graph) if(node.getSequence() == 0) visit(node); }} 訪問順seq 教科書のlowlink 未訪問の場合は探索を進める。 隣の頂点が訪問済みの場合は、 そこからたどれる最も根に 近い頂点の訪問順を更新する。 訪問済みなので木の辺じゃない。 深さ優先探索し終わった後、 調べてみたら、 強連結成分の根だった (seq=lowlink) 交差辺経由で探索されないようにする。 この根からつながる 強連結成分を取り出す。
C E A D B F I G H 図4.4.9 I H G 図 4.4.9 プログラムの実行例 C F D E B A public static void main(String[] args) { System.out.println("図4.4.9"); for(Node<Character> node: test_data2){ node.reset(); } nodeA.connectTo(nodeB); nodeA.connectTo(nodeC); nodeB.connectTo(nodeE); nodeB.connectTo(nodeF); nodeC.connectTo(nodeF); nodeC.connectTo(nodeG); nodeD.connectTo(nodeA); nodeE.connectTo(nodeD); nodeE.connectTo(nodeH); nodeF.connectTo(nodeE); nodeG.connectTo(nodeH); nodeH.connectTo(nodeI); nodeI.connectTo(nodeH); new StrongComponent<Character>().search(test_data2); C E A D B F I G H 図4.4.9 I H G C F D E B A 図 4.4.9 プログラムの実行例 (実線は木の辺、点線はそれ以外の辺)
擬似コード (Pseudocode) 擬似プログラムともいう。 アルゴリズムを既存のプログラミング言語に似せて書いたもの。 かつてはPascalやFortran、今はJava? 要点だけ書くのでそのまま動く保障はない。 教科書のコードはかなり完成度が高い。 Javaだと、どこでも動くコードを書きやすい。
期末試験 教室: C101 (いつものA107は使えません) 日時: 2012年7月30日16時30分~18時00分 持ち込み可 学生証必携 教室: C101 (いつものA107は使えません) 日時: 2012年7月30日16時30分~18時00分 入室限度: 16時50分まで 退出可能: 17時00分より 持ち込み可 教科書・資料(自筆・コピー問わず)は持ち込み可 人間・パソコン・携帯電話・PHSなど持ち込み不可 学生証必携 持っていない場合は教務で発行してもらうこと