18
アアアアアアアアアアアア アアアアアアアアアアアア 2011 2011 7 7 14 14 アアアアアア( ([email protected] ) ) http://www.info.kochi-tech.ac.jp/k1sakai/Lecture/ALG/2011/ index.html 1

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

Embed Size (px)

DESCRIPTION

アルゴリズムとデータ構造. 2011 年 7 月 14 日 酒居敬一 ( [email protected] ) http://www.info.kochi-tech.ac.jp/k1sakai/Lecture/ALG/2011/index.html. バックトラック法 (352ページ). 組織的かつ論理的なしらみつぶし解法 単純に全ての場合を試すのではなく、 問題の性質を考慮して 無駄な計算を省く 例:n女王問題 盤面に女王を置ける場合の数は   とおり しかし、ひとつの列に女王はひとつしか置けない   とおりまで減らすことができる - PowerPoint PPT Presentation

Citation preview

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

20112011 年年 77 月月 1414 日日

酒居敬一酒居敬一 (([email protected]))

http://www.info.kochi-tech.ac.jp/k1sakai/Lecture/ALG/2011/index.html

1

バックトラック法(352ページ)

• 組織的かつ論理的なしらみつぶし解法• 単純に全ての場合を試すのではなく、

問題の性質を考慮して無駄な計算を省く

• 例:n女王問題– 盤面に女王を置ける場合の数は   とおり– しかし、ひとつの列に女王はひとつしか置けない

•   とおりまで減らすことができる

– さらに、ひとつの行に女王はひとつしか置けない•   とおりまで減らすことができる

2

nnC2

nn

!n

… …

……

……

3

1 2 3 4 5 n

1 2 3 4 n 1 2 3 4 n

1 2 3 n

 

……

1行目の女王の位置

2行目の女王の位置

3行目の女王の位置

××

×× ×

×

×

××

深さ優先探索により、すべての場合を調べるのではなく、解の探索の途中で可能性の無い枝を刈り払う → 枝刈り

図6 . 1 . 3 n女王問題の解の探索(354ページ)

4

女王が盤面の(x1 , y1)に居るとき、

array[x1]=y1

x1

y2

x2

y1

Java(0 , 0) x

y3

(x2 , y2)に女王は置けない

↓y1-x1=y2-x2が

成り立たないことminor[y1-x1]=false

(x3 , y3)に女王は置けない

↓x1+y1=x3+y3が

成り立たないことmajor[x1+y1]=false

各列には1個しか置けないので、horizontal[x1]=false

public class BackTrack { private final boolean[] horizontal; private final boolean[] major; private final boolean[] minor; private final int[] array; private final StringBuffer hr = new StringBuffer(); private final StringBuffer queen = new StringBuffer(); public BackTrack(int n){ horizontal = new boolean[n]; major = new boolean[2*n - 1]; minor = new boolean[2*n - 1]; array = new int[n]; Arrays.fill(horizontal, true); Arrays.fill(major, true); Arrays.fill(minor, true);

for(int i = 0; i < n; i++) hr.append("+---"); hr.append('+');

for(int j = 0; j < n - 1; j++) queen.append("| "); queen.append("| X "); for(int j = 0; j < n - 1; j++) queen.append("| "); queen.append('|'); }}

5

左下がりの対角線上に置けるかどうか

1 行に 1 個に 1 個しか置けないようにしたデータ構造

その列に置けるかどうか

右下がりの対角線上に置けるかどうか

盤面そのものを表すデータ構造はない!

private void backtrack(int level){ if(level >= horizontal.length){ for(int x: array){ System.out.println(hr); System.out.append(queen, 4*x, 4*x + 4*array.length + 1); System.out.println(); } System.out.println(hr); } else { int row_a = level; int row_i = level + horizontal.length - 1; for(int i = 0; i < horizontal.length; i++){ if(horizontal[i] && major[row_a + i] && minor[row_i - i]){ horizontal[i] = false; major[row_a + i] = false; minor[row_i - i] = false; array[row_a] = i; backtrack(level + 1); horizontal[i] = true; major[row_a + i] = true; minor[row_i - i] = true; } } } }

6

queenを置かなかったことにする(後戻りするのでバックトラック法)

解の出力横4文字・縦2文字で升目1つ

queenを置いてみる

新しいqueenの位置

すべての場合の盤面を生成して検査するのでもない(生成後検査法ではない)

枝刈り

+---+---+---+---+---+---+---+---+| | | | | | | | X |+---+---+---+---+---+---+---+---+| | | | X | | | | |+---+---+---+---+---+---+---+---+| X | | | | | | | |+---+---+---+---+---+---+---+---+| | | X | | | | | |+---+---+---+---+---+---+---+---+| | | | | | X | | |+---+---+---+---+---+---+---+---+| | X | | | | | | |+---+---+---+---+---+---+---+---+| | | | | | | X | |+---+---+---+---+---+---+---+---+| | | | | X | | | |+---+---+---+---+---+---+---+---++---+---+---+---+---+---+---+---+| | | | | | X | | |+---+---+---+---+---+---+---+---+| | | X | | | | | |+---+---+---+---+---+---+---+---+| X | | | | | | | |+---+---+---+---+---+---+---+---+| | | | | | | | X |+---+---+---+---+---+---+---+---+| | | | | X | | | |+---+---+---+---+---+---+---+---+| | X | | | | | | |+---+---+---+---+---+---+---+---+| | | | X | | | | |+---+---+---+---+---+---+---+---+| | | | | | | X | |+---+---+---+---+---+---+---+---+

public static void main(String[] args) { for(String a: args){ int n; try { n = Integer.parseInt(a); }catch(IllegalArgumentException e){ continue; } new BackTrack(n).backtrack(0); } }

7

n=8の例8 - queen問題の解の一部

幅優先探索(365ページ)

• 深さ優先探索は有用である– 閉路のあるグラフでも深さ優先探索はできる

• グラフがメモリ上に存在しないときは深さ優先探索が使えない– 頂点を辿ったという印を付けられない

• 8パズルのように探索のためのグラフを動的生成するときは、幅優先探索する

8

9

1

7 865432 1

7 865432 1

7 86

54

32 1

7 865

432

1

7 8 654

32

1

7 8 65432

1

78

65432

1

78

6 54

32

1

786 5

432

1

7865

432

1

786

5432

1

786

54

32

図6 . 2 . 2と図6 . 2 . 3 8パズルのグラフの一部

•12手で一巡する閉路が存在する•各状態から作れる状態の数は2から4

•全ての状態をメモリに置くには多い•この場合の「多い」とはメモリ容量に対して

10

S1

S2

S3

初期状態

•初期状態から生成できる新しい状態S1を求める•次にS1から新しい状態S2を求める

•ただし余分の状態は取り除く•初期状態へ戻るものも取り除く

•さらにS2からS3状態を… と順に生成を続ける•解となる状態が生成できたら終了

•このときSk状態を生成するためにSk - 1とSk - 2状態が必要•それ以前の状態はメモリにおく必要はない

public class PuzzleBoard { private final int[] board; private int hole = -1; private static int size; private final PuzzleBoard parent; public PuzzleBoard(int[] new_board){ if(size == 0){ size = (int)Math.sqrt((double)new_board.length); } // 例外処理は割愛 this.board = new_board; for(int i = 0; i < new_board.length; i++){ if(new_board[i] == 0){ hole = i; } } // 例外処理は割愛 this.parent = null; } private PuzzleBoard(PuzzleBoard current, int new_hole){ this.board = current.board.clone(); this.board[current.hole] = this.board[new_hole]; this.board[new_hole] = 0; this.hole = new_hole; this.parent = current; }}

11

初期状態と最終状態の生成用

パズルの盤の定義(その1)

途中の状態の生成用

public boolean equals(Object obj) { PuzzleBoard in = (PuzzleBoard)obj; int[] array = in.board; for(int i = 0; i < this.board.length; i++){ if(array[i] != this.board[i]){ return false; } } // 例外処理は割愛 return true; } public int hashCode() { return board[0] * board[1] + this.hole; // 実行時間に大きく影響する } public PuzzleBoard getParent() { return parent; } public String toString(){ StringBuffer sb = new StringBuffer(); int k = 0; for(int i = 0; i < size; i++){ for(int j = 0; j < size; j++){ sb.append(this.board[k++]).append(' '); } sb.append('\n'); } sb.append('\n'); return sb.toString(); }}

12

結果の表示用

ハッシュテーブルを使うためにequals() と hashCode() を実装

パズルの盤の定義(その2)

public static void generate(Collection<PuzzleBoard> from, Collection<PuzzleBoard> to, Collection<PuzzleBoard> other){ for(PuzzleBoard b: from){ int i = b.hole - size; if(0 <= i){ PuzzleBoard new_board = new PuzzleBoard(b, i); if(!from.contains(new_board)&&!to.contains(new_board)&&!other.contains(new_board)) to.add(new_board); } i = b.hole + size; if(i < b.board.length){ PuzzleBoard new_board = new PuzzleBoard(b, i); if(!from.contains(new_board)&&!to.contains(new_board)&&!other.contains(new_board)) to.add(new_board); } i = b.hole % size; if(i != 0){ PuzzleBoard new_board = new PuzzleBoard(b, b.hole - 1); if(!from.contains(new_board)&&!to.contains(new_board)&&!other.contains(new_board)) to.add(new_board); } if(i != (size - 1)){ PuzzleBoard new_board = new PuzzleBoard(b, b.hole + 1); if(!from.contains(new_board)&&!to.contains(new_board)&&!other.contains(new_board)) to.add(new_board); } }}

13

スペースを上に動かす

パズルの盤の定義(その3)

スペースを下に動かす

スペースを左に動かす

スペースを右に動かす

public class Puzzle { private static int[] initial_state = {5,3,6, 8,7,1, 2,0,4}; private static int[] final_state = {0,1,2, 3,4,5, 6,7,8}; private static PuzzleBoard initial_board = new PuzzleBoard(initial_state); private static PuzzleBoard final_board = new PuzzleBoard(final_state); public static void main(String[] args) { HashSet<PuzzleBoard> set1 = new HashSet<PuzzleBoard>(); HashSet<PuzzleBoard> set2 = new HashSet<PuzzleBoard>(); HashSet<PuzzleBoard> set3 = new HashSet<PuzzleBoard>(); HashSet<PuzzleBoard>[] aspect = new HashSet[]{set1, set2, set3}; // from, to, other aspect[1].add(initial_board); // 初期状態 int step; for(step = 1; !aspect[1].contains(final_board); step++){ // 最終状態に到達するまで探索 HashSet<PuzzleBoard> tmp = aspect[0]; aspect[0] = aspect[1]; aspect[1] = aspect[2]; aspect[2] = tmp; aspect[1].clear(); PuzzleBoard.generate(aspect[0], aspect[1], aspect[2]); System.out.print(step + ": "); System.out.println(aspect[1].size()); } aspect[2].clear(); // ここから結果の表示 aspect[2].add(final_board); // 最終状態だけからなるコレクション aspect[1].retainAll(aspect[2]); // 最終局面で最終状態だけ残す。 for(PuzzleBoard board: aspect[1]){ for(PuzzleBoard current = board; current != null; current = current.getParent()){ System.out.println("step: " + --step); System.out.print(current.toString());}}}}

14

15

step: 65 3 6 2 1 0 7 8 4 step: 55 3 6 2 0 1 7 8 4 step: 45 3 6 2 8 1 7 0 4 step: 35 3 6 2 8 1 0 7 4 step: 25 3 6 0 8 1 2 7 4 step: 15 3 6 8 0 1 2 7 4 step: 05 3 6 8 7 1 2 0 4

1: 32: 53: 104: 145: 286: 427: 808: 1089: 20210: 27811: 52412: 72613: 134814: 180415: 328316: 419317: 732218: 859619: 1393020: 1471321: 2172122: 1982723: 2513224: 1819725: 1897826: 992927: 7359

step: 201 2 5 6 3 4 7 0 8 step: 191 2 5 6 3 4 7 8 0 step: 181 2 5 6 3 0 7 8 4 step: 171 2 5 6 0 3 7 8 4 step: 161 2 5 0 6 3 7 8 4 step: 150 2 5 1 6 3 7 8 4 step: 142 0 5 1 6 3 7 8 4

step: 270 1 2 3 4 5 6 7 8 step: 261 0 2 3 4 5 6 7 8 step: 251 2 0 3 4 5 6 7 8 step: 241 2 5 3 4 0 6 7 8 step: 231 2 5 3 0 4 6 7 8 step: 221 2 5 0 3 4 6 7 8 step: 211 2 5 6 3 4 0 7 8

step: 132 5 0 1 6 3 7 8 4 step: 122 5 3 1 6 0 7 8 4 step: 112 5 3 1 0 6 7 8 4 step: 102 5 3 0 1 6 7 8 4 step: 90 5 3 2 1 6 7 8 4 step: 85 0 3 2 1 6 7 8 4 step: 75 3 0 2 1 6 7 8 4

探索は10秒くらい

初期状態

最終状態

17

2 4

65

154

6 7 8

32

16

先手番

後手番

先手番

後手番

図 6 . 3 . 1 ゲームの木(の部分木だと考えてください)

ミニマックス法では、バックトラック法により木の葉から評価を決めていく。葉から根まで自分が勝つ道ができれば、完全に解析できたことになる。

ゲームの木の探索(376ページ)

+1

-1

+1

-1

+1

-1

+1

+1 +1

+1

+1

-1-1-1

17

0+1

-1

+1

+1 0 -1

-1

先手番

後手番

先手勝ち 後手勝

ち引き分

•ゲーム終了の状態に+1・0・-1を与える•ゲームの途中では自分に有利なほうの枝を辿る

•ゲームの木の途中の頂点の値を決定できる•手番が先手・後手に応じて最大・最小を選択

•全手読みができれば•先手必勝・引 き分け・後手必勝がわかる•全手読みは時間的にも空間的にも困難

•全手読みが不可能な場合•その局面での勝ちや すさ(負けやすさ)を求める

•先手有利を正の数、後手有利を負の数…•その数値を求める関数を評価関数という•先読みする深さを限定して評価する

•確率的要素が入るゲームは、ここでは扱わない•完全情報ゲームのみを対象とする

18

S

A B

GFE H

2 4 1 3 7 1 2 3

先手番

後手番

先手番

後手番

4以上4以下なら打ち切 り

4以下

4以上なら打ち切 り

4 7以上 3

調べるだけ無駄

3以下4

•数字は大きいほど先手に有利、つまり、小さいほど後手に有利•先手はより大きな数値を持つ方向を選ぶ•後手はより小さな数値を持つ方向を選ぶ

•評価関数の値について

•深さ制限つきのミニマックス法•一定の深さまで読んで、最大値もしくは最小値を選ぶ

•α-β 法•一定の深さまで読んで、最大値や最小値に貢献しない枝を刈る

調べるだけ無駄