幅優先探索をJavaを使って覚える

  2010/01/17 01:59

下らない内容だなぁと思い人もたくさんいるかと思いますが、
自分は今まで幅優先探索が苦手で敬遠してきました!(笑

そんなお仲間がいらっしゃればご参考になればと。
この記事はそれ以上でもそれ以下でもございません。

まず、きっかけです。
最近いろいろなところで目にしたので、是非解きたいと思った問題がありまして、、

人材獲得作戦・4 試験問題ほか | 人生を書き換える者すらいた。
http://okajima.air-nifty.com/b/2010/01/post-abc6.html

これなんですが、読んでみたら簡単そうなんですが、どうやら幅優先探索(BFS:BreadthFirstSearch)を使いそう。
TopCoderでは、「BFSがベストアンサー!」みたいな問題は今まで見かけなかったので放置していたんですが、
腹をくくって勉強。
DFSは得意なんだよね。なぜか、あの再帰具合が。

で、たまたまITMediaで連載中の講座でも探索が!
これは偶然だけども。

知れば天国、知らねば地獄――「探索」虎の巻 | 最強最速アルゴリズマー養成講座
http://www.itmedia.co.jp/enterprise/articles/1001/16/news001.html

探索は基本だよねえ。うん。

Javaでの実装に使うもの

そこを読ませて頂いても、Wikipediaの解説を読んでも、キュー(Queue)を使うのが基本らしい。
幅優先っていう概念はわかりやすいんだけどね。実装がねー

JavaでQueueだと、Queueインターフェイスを実装したLinkedListなんかがお手頃。
※ 話が逸れるけど、Eclipseで「LLi」ってとこまでいれて保管すると一番上にLinkedListがでる。

で、問題はなんのメソッドを使うか。
Stackインターフェイスも実装しているからややこしいんだけど、
Queueは先入れ先出し(FIFO)が原則なので、後方から入れて先頭から出すようにする。
行列と同じ。以下の2つのメソッドを使う。

LinkedList#offer(e)
リストの末尾に追加。

LinkedList#poll()
リストの先頭を取得して削除。

Stackに関係するメソッドは有名だから知ってたけど、
この2つは最近まで全然知らなかった。(のは俺だけかもしれない)

実装

後は書くしか無い。

ALGORITHM NOTE | 幅優先探索 Breadth First Search
http://algorithms.blog55.fc2.com/blog-entry-127.html

参考にしました。このサイトにはいつも助けられてます。

自分で分かりやすく書きたかったので、結構だらだら長いコードだけど、こんな感じ。

public class BreadthFirstSearch {
	Point[] move = new Point[]{new Point(0, -1), new Point(-1,0), new Point(1, 0), new Point(0,1)};
	char[] directions = new char[]{'r', 'd', 'u', 'l'};

	public BreadthFirstSearch(int[][] input, int sx, int sy){
		class Puzzle {
			int[][] map = new int[3][3];
			int x = 0;
			int y = 0;
			String path;

			public Puzzle(int[][] map, int x, int y, String path) {
				this.map = map;
				this.x = x;
				this.y = y;
				this.path = path;
			}

			public boolean check(){
				for (int i = 0; i < 8; i++) {
					if (this.map[i / 3][i % 3] != i+1) return false;
				}
				return true;
			}
		}

		LinkedList<Puzzle> queue = new LinkedList<Puzzle>();
		queue.push(new Puzzle(input, sx, sy, ""));

		BFS : while (queue.size() > 0) {
			Puzzle p = queue.poll();

			for (int i = 0; i < 4; i++) {
				int x = p.x + move[i].x;
				int y = p.y + move[i].y;

				if (x >= 0 && x <= 2 && y >= 0 && y <= 2) {
					int[][] map = this.dc(p.map);
					map[p.x][p.y] = map[x][y];
					map[x][y] = 0;

					Puzzle moved = new Puzzle(map, x, y, p.path + directions[i]);
					if (moved.check()) {
						System.out.println(moved.path);
						break BFS;
					}

					queue.add(moved);
				}
			}
		}

		System.out.println("#END");
	}

	public int[][] dc(int[][] input){
		int[][] output = new int[input.length][input[0].length];
		for (int i = 0; i < input.length; i++) {
			output[i] = input[i].clone();
		}
		return output;
	}

	public static void main(String[] args) throws FileNotFoundException {
		FileInputStream fis = new FileInputStream("src/study/puzzle.in");
		Scanner sc = new Scanner(fis);

		int[][] puzzle = new int[3][3];
		int x = 0;
		int y = 0;

		for (int i = 0; i < 9; i++) {
			char input = sc.next().charAt(0);
			if (input == 'x') {
				x = i / 3;
				y = i % 3;
				puzzle[i / 3][i % 3] = 0;
			} else {
				puzzle[i / 3][i % 3] = input - '0';
			}
		}

		new BreadthFirstSearch(puzzle, x, y);
	}
}

次に本命のあのサイト。
せっかくだからTopCoderっぽくコストから出してみた。
かなり回りくどいけど、書いてて自分で勉強になった。

public class Kuma4_1 {
	static final int width = 26;
	static final int height = 13;
	Point[] movable = new Point[]{new Point(1, 0), new Point(0, 1), new Point(-1, 0), new Point(0, -1)};

	public Kuma4_1(char[][] map, int sx, int sy, int gx, int gy) {
		LinkedList<Point> queue = new LinkedList<Point>();
		queue.add(new Point(sx, sy));
		int cost = 1;
		int[][] costMap = new int[height][width];
		char[][] copiedMap = this.dc(map);

		BFS: while (queue.size() > 0) {
			Point now = queue.poll();

			for (int i = 0; i < movable.length; i++) {
				int x = now.x + movable[i].x;
				int y = now.y + movable[i].y;

				if (map[x][y] == 'G') {
					costMap[x][y] = cost;
					this.out(copiedMap, costMap, gx, gy);
					break BFS;

				} else if (map[x][y] == ' ') {
					costMap[x][y] = cost;
					map[x][y] = '#';
					queue.offer(new Point(x,y));
				}
			}

			cost++;
		}

		System.out.println("#process end");
	}

	public void out(char[][] map, int[][] costMap, int gx, int gy){
		int cost = costMap[gx][gy];
		Point p = new Point(gx, gy);
		while (cost > 1){
			cost--;
			for (int i = 0; i < movable.length; i++) {
				int x = p.x + movable[i].x;
				int y = p.y + movable[i].y;

				if (costMap[x][y] == cost) {
					map[x][y] = '$';
					p = new Point(x,y);
				}
			}
		}

		for (int i = 0; i < map.length; i++) {
			System.out.println(new String(map[i]));
		}
	}

	public void out(int[][] map) {
		for (int i = 0; i < map.length; i++) {
			for (int j = 0; j < map[i].length; j++) {
				System.out.printf("%3d", map[i][j]);
			}
			System.out.println();
		}
	}

	public void out(char[][] map) {
		for (char i = 0; i < map.length; i++) {
			System.out.println(new String(map[i]));
		}
	}

	public char[][] dc(char[][] input){
		char[][] output = new char[input.length][input[0].length];
		for (char i = 0; i < input.length; i++) {
			output[i] = input[i].clone();
		}
		return output;
	}

	public static void main(String[] args) throws FileNotFoundException {
		FileInputStream fis = new FileInputStream("src/study/kuma4.in");
		Scanner sc = new Scanner(fis);

		char[][] map = new char[height][width];

		for (int i = 0; sc.hasNext(); i++) {
			String line = sc.nextLine();
			map[i] = line.toCharArray();
		}

		int sx=0, sy=0, gx=0, gy=0;
		for (int i = 0; i < map.length; i++) {
			for (int j = 0; j < map[0].length; j++) {
				if (map[i][j] == 'S') {
					sx = i;
					sy = j;
				} else if (map[i][j] == 'G') {
					gx = i;
					gy = j;
				}
			}
		}

		new Kuma4_1(map, sx, sy, gx, gy);
	}

しかしなげぇ。でも、書く時間自体はそんなかからなかった。30分弱くらいかなあ。
でも、アルゴリズム調べてからだから、そう考えると論外だとも。

とにかく、ぼくはこれで幅優先探索をやっと覚えました!ばっちこい。