Transcript PPT

アルゴリズムとデータ構造
2011年7月4日
酒居敬一([email protected])
http://www.info.kochi-tech.ac.jp/k1sakai/Lecture/ALG/2011/index.html
グラフの定義(225ページ)
• グラフは頂点の集合と辺の集合からなる。
• グラフには有向グラフと無向グラフがある。
• グラフに対する、教科書4章の仮定。
– (v, v)の形の辺(NDAのε遷移みたいなの)はない。
– 頂点uからvへ結ぶ辺は高々1つ。
• 辺の属性として数値を持つ場合、重みという。
• いくつかの辺をつないでできる経路は道。
– 有向グラフでは辺はすべて同じ向きをたどる。
• 頂点からその頂点自身への道は閉路。
• 木はグラフの一種。
グラフの定義(つづき)
• 木が複数集まったグラフは森という。
– 木と木がつながっていなくてもいい。
• 頂点につながる辺の数を次数という。
– 有向グラフでは、入次数・出次数と区別する。
• 正則グラフでは全頂点の次数が同じ。
– このときの次数をグラフの次数とする場合がある。
• ハイパーキューブなど
• グラフ全体を組織的に調べることを探索という。
– ただし、単に頂点を訪問するだけ、かもしれない。
グラフアルゴリズムの計算量
• 頂点数をnとしたときの最大の辺の数mは、
無向グラフでは m  n C2  n  (n 1) / 2
有向グラフでは m  n  (n  1)
• 辺の数が最大のものを完全グラフという。
• 辺の数が0でもグラフである。
• 密なグラフか、疎なグラフか。
– 次数がある程度以下なら疎と考える。
• 「ある程度」とは、問題に依存する。
• CCC(Cude Connected Cycle)なら次数は3。疎?
• 人工的なネットワークか、自然なネットワークか。
グラフの表現法(230ページ)
• 隣接行列
– 頂点から頂点への接続の有無や辺の重みを持つ
– 密なグラフにはいい
• 配列やListやSetやMapによる表現
– 正則グラフなら配列
– 二分探索木のような順序木のグラフならList
– 無向グラフならListでもSetでもMapでもいい
• MapならKeyを接続先の頂点、Valueを重みにするなど
• 計算で求める
– 配列上のヒープソートの、部分順序つき二分木
– スパコン内部ネットワークなど
0100
0101
1100
1101
0110
0111
1110
1111
0000
0001
1000
1001
0010
0011
1010
1011
4-dimentional binary hyper cube
A
深さ優先探索
B
(234ページ)
1
C
A
D
7
B
2
E
G
1
6
C
F
A
D
5
B
図4.3.2 a
3
E
2
5
4
C
F
D
4
G
図4.3.2 b
•木の辺(実線で表示)
•逆辺(点線で表示)
•連結グラフなら木が得られ、
そうでなければ森が得られる
6
3
E
F
7
G
図4.3.3
public class Node<E> {
private E value;
// 頂点の値
private Collection<Node<E>> edges; // 有向辺
private boolean visited;
private int sequence;
private boolean searching;
public Node(E value, Collection<Node<E>> edges) {
this.value = value;
this.edges = edges;
reset();
}
public void reset(){
this.visited = false;
this.sequence = 0;
this.edges.clear();
this.searching = false;
}
public E getValue() { // 頂点の値を得る。
return value;
}
public Collection<Node<E>> getEdges(){
return this.edges;
}
グラフの頂点を表すクラス
(4.3と4.4で使う)
}
public boolean isVisited() {
return visited;
}
public void setVisited(boolean v) {
this.visited = v;
}
public int getSequence() {
return sequence;
}
public void setSequence(int s) {
this.sequence = s;
}
public boolean isSearching() {
return searching;
}
public void setSearching(boolean s) {
this.searching = s;
}
public void connect(Node<E> to){
this.edges.add(to); //無向辺を追加
if( !to.getEdges().contains(this) ){
to.getEdges().add(this);
}
}
public void connectTo(Node<E> to){
this.edges.add(to); //有向辺を追加
8
}
private static Node<Character> nodeA = new Node<Character>('A', new LinkedList<Node<Character>>());
private static Node<Character> nodeB = new Node<Character>('B', new LinkedList<Node<Character>>());
private static Node<Character> nodeC = new Node<Character>('C', new LinkedList<Node<Character>>());
private static Node<Character> nodeD = new Node<Character>('D', new LinkedList<Node<Character>>());
private static Node<Character> nodeE = new Node<Character>('E', new LinkedList<Node<Character>>());
private static Node<Character> nodeF = new Node<Character>('F', new LinkedList<Node<Character>>());
private static Node<Character> nodeG = new Node<Character>('G', new LinkedList<Node<Character>>());
private static Collection<Node<Character>> test_data = new LinkedList<Node<Character>>();
static {
public class DepthFirstSearch {
test_data.add(nodeA);
private static <E> void visit(Node<E> node){
test_data.add(nodeB);
test_data.add(nodeC);
node.setVisited(true);
test_data.add(nodeD);
System.out.print(node.getValue());
test_data.add(nodeE);
for(Node<E> neighbor: node.getEdges()){
test_data.add(nodeF);
if(neighbor.isVisited()) continue; // 訪問済み
test_data.add(nodeG);
System.out.print(" -> ");
}
}
テストデータのうち、
グラフの頂点とその集合
}
visit(neighbor);
}
public static <E> void search(Collection<Node<E>> graph){
for(Node<E> node: graph){
if(node.isVisited()) continue; // 訪問済み
System.out.println(node.getValue() + "から探索します。");
visit(node);
System.out.println();
}
}
9
深さ優先探索のプログラム
public static void main(String[] args) {
System.out.println("図4.3.2");
for(Node<Character> node: test_data){
node.reset();
public static void main(String[] args) {
}
System.out.println("図4.3.3");
nodeA.connect(nodeC);
for(Node<Character> node: test_data){
nodeA.connect(nodeD);
node.reset();
public static void main(String[] args) {
nodeA.connect(nodeB);
}
System.out.println("図4.3.4");
nodeC.connect(nodeE);
nodeA.connect(nodeC); for(Node<Character> node: test_data){
nodeC.connect(nodeF);
nodeA.connect(nodeD);
node.reset();
nodeD.connect(nodeB);
nodeA.connect(nodeB); }
nodeD.connect(nodeF);
nodeC.connect(nodeF);
nodeA.connectTo(nodeC);
nodeE.connect(nodeG);
nodeC.connect(nodeE);
nodeA.connectTo(nodeD);
nodeE.connect(nodeF);
nodeD.connect(nodeB); nodeA.connectTo(nodeE);
search(test_data);
nodeD.connect(nodeF); nodeC.connectTo(nodeB);
}
nodeE.connect(nodeG);
nodeB.connectTo(nodeA);
nodeE.connect(nodeF);
nodeD.connectTo(nodeC);
search(test_data);
nodeD.connectTo(nodeE);
}
nodeF.connectTo(nodeA);
図4.3.2
Aから探索します。
A -> C -> E -> G -> F -> D -> B
}
nodeF.connectTo(nodeG);
search(test_data);
図4.3.3
Aから探索します。
A -> C -> F -> D -> B -> E -> G
図4.3.4
Aから探索します。
A -> C -> B -> D -> E
10
Fから探索します。
F -> G
A
•上昇辺
子孫から祖先へ向かう辺
無向グラフでは逆辺
•下降辺
祖先から子孫へ向かう辺
無向グラフでは逆辺
•交差辺
上昇辺でも下降辺でもない辺
F
B
G
E
C
D
図4.3.4 a
連結グラフとは、グラフ全体が
つながっていること。無向グラフ
では、深さ優先探索で全ての
頂点をたどる事ができれば連結
グラフである。
しかし、有向グラフでは必ずしも
そうとはならない。
交差辺
上昇辺
1
6
A
F
3
下降辺
B
5
2
4
C
7
G
E
D
交差辺
図4.3.4 b
public class DirectedDepthFirstSearch<E> {
private int sequence;
private void visit(Node<E> node){
node.setVisited(true);
node.setSequence(++this.sequence);
(240ページ)
node.setSearching(true);
System.out.print(node.getValue());
for(Node<E> neighbor: node.getEdges()){
if(neighbor.getSequence() == 0){
System.out.print(" -> ");
visit(neighbor); // 木の辺
} else if (neighbor.getSequence() > node.getSequence()) {
System.out.print(" 下降辺(" + node.getValue() + ", " + neighbor.getValue() + ")");
} else if (neighbor.isSearching()){
System.out.print(" 上昇辺(" + node.getValue() + ", " + neighbor.getValue() + ")");
} else {
System.out.print(" 交差辺(" + node.getValue() + ", " + neighbor.getValue() + ")");
}}
node.setSearching(false);
}
public void search(Collection<Node<E>> graph){
this.sequence = 0;
for(Node<E> node: graph){
if(node.getSequence() == 0){
System.out.println(node.getValue() + "から探索します。");
visit(node);
12
System.out.println();
}}}}
有向グラフの深さ優先探索
public static void main(String[] args) {
System.out.println("図4.3.4");
for(Node<Character> node: test_data){
node.reset();
}
nodeA.connectTo(nodeC);
nodeA.connectTo(nodeD);
nodeA.connectTo(nodeE);
nodeC.connectTo(nodeB);
nodeB.connectTo(nodeA);
nodeD.connectTo(nodeC);
nodeD.connectTo(nodeE);
nodeF.connectTo(nodeA);
nodeF.connectTo(nodeG);
new DirectedDepthFirstSearch<Character>().search(test_data);
}
図4.3.4
Aから探索します。
A -> C -> B 上昇辺(B, A) -> D 交差辺(D, C) -> E 下降辺(A, E)
Fから探索します。
F 交差辺(F, A) -> G
13
トポロジカルソート
(242ページ)
•Topology は「位相」のこと。トポロジカルソートのときはこちら。
•Phase も「位相」と訳せます。ベクタの位相はこちら。
たとえば、ベクターがあったとします。
5 
0 
 
0 
大きさ0  
0 
2
大きさ2  
0 
5 
大きさ5  
0 
0 
2
 
3
4
 
0 
5 
 
大きさを比較することは
できますが、大きさが同じ
だからといって同じベクター
であるとは限りませんよね?
全要素間で順序がつけられる → 全順序関係
一部の要素間に順序がつけられる → 半順序関係
同じだけど同じじゃない、というのは順序がつけられて
ません。そういうデータは、DAGで保持することができる。
2
0 
 
0 
0 
 
3
4
 
0 
2
 
0 
5 
 
図4.3.6 DAG
(Directed Acyclic Graph)
幅優先探索
A
(244ページ)
B
C
D
1
A
E
G
4
B
F
2
C
図4.3.2 a
D
3
5
E
6
F
7
G
図4.3.7
public class BreadthFirstSearch {
public static <E> void search(Collection<Node<E>> graph){
Queue<Node<E>> queue = new LinkedList<Node<E>>(); // FIFO
for(Node<E> node: graph){
if(node.isVisited()){
continue; // 訪問済み
}
queue.add(node);
// enqueue
node.setVisited(true);
while( !queue.isEmpty() ){
Node<E> next = queue.remove();
// dequeue
System.out.print("頂点" + next.getValue());
for(Node<E> neighbor: next.getEdges()){
if( neighbor.isVisited() ){
continue;
}
queue.add(neighbor);
// enqueue
neighbor.setVisited(true);
System.out.print(" 辺(" + next.getValue() + ", " + neighbor.getValue() + ")");
}
System.out.print(" -> ");
}
System.out.println();
}
}
}
16
public static void main(String[] args) {
System.out.println("図4.3.7");
for(Node<Character> node: test_data){
node.reset();
}
nodeA.connect(nodeC);
nodeA.connect(nodeD);
nodeA.connect(nodeB);
nodeC.connect(nodeE);
nodeC.connect(nodeF);
nodeD.connect(nodeB);
nodeD.connect(nodeF);
nodeE.connect(nodeG);
nodeE.connect(nodeF);
search(test_data);
}
図4.3.7
頂点A 辺(A, C) 辺(A, D) 辺(A, B) ->
頂点C 辺(C, E) 辺(C, F) ->
頂点D ->
頂点B ->
頂点E 辺(E, G) ->
頂点F ->
頂点G ->
17
public class DepthFirstSearchStack {
public static <E> void search(Collection<Node<E>> graph){
Stack<Node<E>> stack = new Stack<Node<E>>();
// LIFO
for(Node<E> node: graph){
if(node.isVisited()){
continue; // 訪問済み
}
stack.push(node);
// push
node.setVisited(true);
while( !stack.empty() ){
Node<E> next = stack.pop();
// pop
System.out.print("頂点" + next.getValue());
for(Node<E> neighbor: next.getEdges()){
if( neighbor.isVisited() ){
continue;
}
stack.add(neighbor);
// push
neighbor.setVisited(true);
}
BreadthFirstSearchのFIFO(リスト)を
System.out.print(" -> ");
LIFO(スタック)に変えたもの。
}
この変更により、幅優先探索だったプログラムが
System.out.println();
深さ優先探索プログラムになる。
}
(247ページ)
}
}
18