アルゴリズムとデータ構造1

アルゴリズムとデータ構造
2013年7月16日
酒居敬一([email protected])
http://www.info.kochi-tech.ac.jp/k1sakai/Lecture/ALG/2013/index.html
最短路の問題
(260ページ)
• 最短路問題
– グラフの2頂点間を結ぶ道のうちで辺の重みの和が
最小のものを求める問題。
• 対象は辺に重みのついた有向グラフ
• 問題は大きく2種類ある
1. 出発点を1つに固定して、そこから他のすべての頂
点への最短路を求める。
2.すべての2頂点の組み合わせに対して、最短路を
求める。
• 1の方法をすべての頂点に適用するのが最善。
2
単一出発点の問題
(261ページ)
• Dijkstraのアルゴリズム
– 各頂点への最短路を出発点に近いところから
ひとつづつ確定する。
確定した頂点から行くことのできる
頂点への距離を計算。
3
B
6
D
9
8
6
出発点
2
A
1
4
E
7
3
最小の距離を持つ頂点は確定。
他から来るにしても重みが負の
経路がなければ、距離は減らない。
C
4
6
10
F
図4.5.1 アルゴリズムの動き
3
public class WeightedNode<E> {
辺に重みを持たせるため、Mapを使う。
private E value;
// 頂点の値
private Map<WeightedNode<E>, Integer> edges; // 有向辺
private boolean visited;
出発点からの距離
private int distance;
public WeightedNode(E value, Map<WeightedNode<E>, Integer> edges) {
this.value = value;
this.edges = edges;
辺の重み
reset();
}
public void reset(){
this.visited = false;
this.distance = Integer.MAX_VALUE;
this.edges.clear();
}
public E getValue() { // 頂点の値を得る。
return value;
}
public Map<WeightedNode<E>, Integer> getEdges(){
return this.edges;
}
// アクセッサは、このスライドでは省略した。実際には存在する。
public void connectTo(WeightedNode<E> to, int weight){
this.edges.put(to, weight); // 有向辺を追加
}
本日使う頂点のデータ構造
}
4
public class Dijkstra {
public static <E> void search(Collection<WeightedNode<E>> graph, WeightedNode<E> start){
Set<WeightedNode<E>> visited = new HashSet<WeightedNode<E>>();
集合V
Set<WeightedNode<E>> unreached = new HashSet<WeightedNode<E>>(graph);
start.setDistance(0);
while( !unreached.isEmpty() ){
int min_distance = Integer.MAX_VALUE;
WeightedNode<E> min_node = null;
集合U
for(WeightedNode<E> node: unreached){
if(node.getDistance() < min_distance){
min_distance = node.getDistance();
集合Uから最小の距離をもつ頂点を探索
min_node = node;
}
}
unreached.remove(min_node);
visited.add(min_node);
for(Map.Entry<WeightedNode<E>,Integer> edge: min_node.getEdges().entrySet()){
if(unreached.contains(edge.getKey())){
edge.getKey().setDistance(
Math.min(edge.getKey().getDistance(),
min_node.getDistance() + edge.getValue()));
}
}
}
}
教科書263ページの擬似プログラムをJavaで実装
}
5
private static WeightedNode<Character> nodeA = new WeightedNode<Character>('A', new HashMap<WeightedNode<Character>, Integer>());
private static WeightedNode<Character> nodeB = new WeightedNode<Character>('B', new HashMap<WeightedNode<Character>, Integer>());
private static WeightedNode<Character> nodeC = new WeightedNode<Character>('C', new HashMap<WeightedNode<Character>, Integer>());
private static WeightedNode<Character> nodeD = new WeightedNode<Character>('D', new HashMap<WeightedNode<Character>, Integer>());
private static WeightedNode<Character> nodeE = new WeightedNode<Character>('E', new HashMap<WeightedNode<Character>, Integer>());
private static WeightedNode<Character> nodeF = new WeightedNode<Character>('F', new HashMap<WeightedNode<Character>, Integer>());
private static Collection<WeightedNode<Character>> test_data = new LinkedList<WeightedNode<Character>>();
static {
test_data.add(nodeA);
test_data.add(nodeB);
test_data.add(nodeC);
test_data.add(nodeD);
test_data.add(nodeE);
test_data.add(nodeF);
}
public static void main(String[] args) {
System.out.println("図4.5.1");
for(WeightedNode<Character> node: test_data){
node.reset();
}
nodeA.connectTo(nodeB, 6);
nodeA.connectTo(nodeC, 4);
nodeB.connectTo(nodeD, 3);
nodeC.connectTo(nodeE, 3);
nodeC.connectTo(nodeF, 6);
nodeE.connectTo(nodeB, 2);
nodeE.connectTo(nodeD, 1);
search(test_data, nodeA);
System.out.println("頂点" + nodeA.getValue() + "からの距離");
for(WeightedNode<Character> node: test_data){
System.out.println("頂点" + node.getValue() + ": " + node.getDistance());
}
}
ひとつずつ確定する作戦の前提として、
辺の重みは正の値である。
図4.5.1
頂点Aからの距離
頂点A: 0
頂点B: 6
頂点C: 4
頂点D: 8
頂点E: 7
頂点F: 10
6
263ページの実装では、集合Uから最小の距離をもつ
頂点を線形探索し、集合Uから移動していた。
そもそも、線形探索は時間計算量が多い。
そこで、集合Uの替わりにヒープを使うことを考える。
ただし、リストのようなデータ構造でヒープを実装するのは、
データ操作に必要な時間計算量の多さから不適切である。
ヒープとしては頂点への参照を入れた配列を使うことにする。
最小の距離を持つ頂点をヒープから移動し、ヒープを再構成する。
実装は省略しますが、263ページの擬似プログラムが単純なので、
それを理解してみるといいだろう。それから267ページを理解する。
7
public class DijkstraArray {
public static <E> void search(WeightedNode<E>[] nodes, int start, int[][] weights){
nodes[start].setDistance(0);
for(int step = 0; step < nodes.length; step++){
頂点の配列と、頂点同士の接続行列
int min = Integer.MAX_VALUE;
int p = 0;
for(int x = 0; x < nodes.length; x++){
if( !nodes[x].isVisited() && (nodes[x].getDistance() < min)){
p = x;
min = nodes[x].getDistance();
}
}
if(min == Integer.MAX_VALUE){
throw new IllegalArgumentException("グラフが連結でない。");
}
nodes[p].setVisited(true);
for(int x = 0; x < nodes.length; x++){
if(weights[p][x] == Integer.MAX_VALUE){
continue;
}
nodes[x].setDistance(
Math.min(nodes[x].getDistance(),
nodes[p].getDistance() + weights[p][x]));
}
}
265ページのプログラムのJava実装
}
8
}
private static WeightedNode<Character> nodeA = new WeightedNode<Character>('A');
private static WeightedNode<Character> nodeB = new WeightedNode<Character>('B');
private static WeightedNode<Character> nodeC = new WeightedNode<Character>('C');
private static WeightedNode<Character> nodeD = new WeightedNode<Character>('D');
private static WeightedNode<Character> nodeE = new WeightedNode<Character>('E');
private static WeightedNode<Character> nodeF = new WeightedNode<Character>('F');
@SuppressWarnings("unchecked")
private static WeightedNode<Character>[] test_data = new WeightedNode[] {
nodeA, nodeB, nodeC, nodeD, nodeE, nodeF
};
public static void main(String[] args) {
System.out.println("図4.5.1");
for(WeightedNode<Character> node: test_data){
node.reset();
}
int[][] weights = new int[test_data.length][test_data.length];
for(int[] from: weights){
for(int i = 0; i < from.length; i++){
from[i] = Integer.MAX_VALUE;
}
}
weights[0][1] = 6;
これもDijkstraのアルゴリズムなので、
weights[0][2] = 4;
weights[1][3] = 3;
ひとつずつ確定する作戦の前提として、
weights[2][4] = 3;
辺の重みは正の値である。
weights[2][5] = 6;
weights[4][1] = 2;
weights[4][3] = 1;
search(test_data, 0, weights);
System.out.println("頂点" + nodeA.getValue() + "からの距離");
for(WeightedNode<Character> node: test_data){
System.out.println("頂点" + node.getValue() + ": " + node.getDistance());
}
}
図4.5.1
頂点Aからの距離
頂点A: 0
頂点B: 6
頂点C: 4
頂点D: 8
頂点E: 7
頂点F: 10
9
全頂点間の問題
(270ページ)
• 単一の出発点のアルゴリズムを、全頂点に
順に適用する方法がひとつ。
• 重み行列が与えられるとき、Floydの
アルゴリズムを使って全頂点間の距離を
求める方法もがある。
10
public class Floyd {
public static <E> void search(int[][] weights, int[][] distances){
for(int i = 0; i < weights.length; i++){
distances[i] = weights[i].clone();
}
for(int k = 0; k < weights.length; k++){
for(int i = 0; i < weights.length; i++){
if(distances[i][k] == Integer.MAX_VALUE){
continue; // 未接続 or 未計算
}
for(int j = 0; j < weights[i].length; j++){
if(distances[k][j] == Integer.MAX_VALUE){
continue; // 未接続 or 未計算
}
int w = distances[i][k] + distances[k][j];
if(w < distances[i][j]){
distances[i][j] = w;
}
}
頂点kを経由して、頂点iから頂点jに至る道があれば、
}
距離をあらわす行列にその距離を計算し格納する。
}
}
}
270ページのプログラム Floydのアルゴリズム
11
public static void main(String[] args) {
System.out.println("図4.5.1");
int[][] weights = new int[6][6];
for(int i = 0; i < weights.length; i++){
for(int j = 0; j < weights[i].length; j++){
weights[i][j] = Integer.MAX_VALUE;
}
weights[i][i] = 0;
}
weights[0][1] = 6;
weights[0][2] = 4;
weights[1][3] = 3;
weights[2][4] = 3;
weights[2][5] = 6;
weights[4][1] = 2;
weights[4][3] = 1;
int[][] distances = new int[weights.length][];
search(weights, distances);
System.out.println("頂点間の距離");
for(int i = 0; i < distances.length; i++){
for(int j = 0; j < distances[i].length; j++){
if(distances[i][j] == Integer.MAX_VALUE){
System.out.print("- ");
} else {
System.out.print(distances[i][j] + " ");
}
}
System.out.println();
}
}
図4.5.1
頂点間の距離
0 6 4 8 7 10
- 0 - 3 - - 5 0 4 3 6
- - - 0 - - 2 - 1 0 - - - - - 0
12
グラフの推移的閉包
(275ページ)
グラフの推移的閉包とは、次のような行列aのことである。
 true (頂点iからjへの道が存在する場合)
a[i, j ]  
 false (頂点iからjへの道が存在しない場合)
Floydのアルゴリズムとほぼ同じで、
相違点は2頂点間の距離を求めるのではなく、
単に道があるかないかだけを調べる。
そのアルゴリズムをWarshallのアルゴリズムと呼ぶ。
13
public class Warshall {
public static <E> void search(boolean[][] adjacency, boolean[][] reachable){
for(int i = 0; i < adjacency.length; i++){
reachable[i] = adjacency[i].clone();
}
for(int k = 0; k < adjacency.length; k++){
入力は隣接行列
for(int i = 0; i < adjacency.length; i++){
if( !reachable[i][k] ){
continue;
}
for(int j = 0; j < adjacency[i].length; j++){
出力は推移的閉包
reachable[i][j] = reachable[i][j] || reachable[k][j];
}
}
}
}
}
14
public static void main(String[] args) {
System.out.println("図4.5.1");
boolean[][] adjacency = new boolean[6][6];
for(int i = 0; i < adjacency.length; i++){
for(int j = 0; j < adjacency[i].length; j++){
adjacency[i][j] = false;
}
adjacency[i][i] = true;
}
adjacency[0][1] = true;
adjacency[0][2] = true;
adjacency[1][3] = true;
adjacency[2][4] = true;
adjacency[2][5] = true;
adjacency[4][1] = true;
adjacency[4][3] = true;
boolean[][] reachable = new boolean[adjacency.length][];
search(adjacency, reachable);
System.out.println("推移的閉包");
for(int i = 0; i < reachable.length; i++){
for(int j = 0; j < reachable[i].length; j++){
if(reachable[i][j]){
System.out.print("+ ");
} else {
System.out.print("- ");
}
}
System.out.println();
}
}
図4.5.1
推移的閉包
+ + + + + +
- + - + - - + + + + +
- - - + - - + - + + - - - - - +
15