May 23 2008

Dijkstra 관련 문제 하나…(출처 : 프로그래밍 갤러리)

분류: DS/Algorithm 태그: ,, , , Heart @ 12:40 오전

Trackback : http://dev.heartsavior.net/archives/140/trackback/

프로그래밍 갤러리에서 올라온 문제를 한 번 풀어 보았다.
실무 작업만 하다가 오랜만에 자료구조/알고리즘 문제를 푸니까 좀 어렵기도 하고 전공 기억도 가물가물했다.
사실 아무런 정보도 없었으면 좀 헤맬 뻔 했는데, 리플에 Dijkstra 얘기가 나와서 해결 실마리를 찾을 수 있었다.

브로디에게 납치를 당한 뒤 창고에 갇힌 다오는 서둘러 탈출할 방법을 찾고 있다.
가로막힌 블록을 맨손으로 부수며 길을 만들어야 하는데 최소 몇 개의 블록을 부수어야 출구에 도달할 수 있을까
?
이동은 상하좌우 네 방향으로만 가능하며, 블록이 여러 겹으로 놓인 장애물도 존재한다.

하나의 프로그램을 작성하여 첨부 파일 map1.txt, map2.txt, map3.txt의 세 가지 입력에 대하여 각각 답을 구하시오.

입력<

5 6 // 행의 수 n, 열의 수 m

s.#… // 첫 번째 행
..#### // 두 번째 행
#.#..# // 각 행은 m개의 문자로 이루어져 있고, 각 문자의 의미는 아래와 같다

..###. // 빈 공간: . 블록: # 다오: s 출구: e
###..e // n번째 행


출력

2 // 탈출하기 위해 부수어야 하는 최소 블록 개수

일단 창고의 각 공간을 노드로 보면, 창고는 bi-directional weight 가 존재하는 그래프이다.
(bi-directional로 해야 하는 이유는, 두 개의 노드가 연결되는 간선이 빈 공간 -> 블록, 블록 -> 빈 공간의 서로 다른 weight가 생길 수 있기 때문이다.)
그래프에 대해 블록 노드로 가는 간선의 weight는 1, 나머지는 0 으로 매겨서 인접 행렬을 만든 다음, Dijkstra 최소거리 알고리즘을 적용하면 s -> e에 대한 최소 weight가 구해진다.
위에서 블록 노드로 가는 경우에만 1의 weight를 적용하였으므로 최소 weight가 부순 블록의 갯수가 된다.

자바로 구현해 봤다. 사실 좀 걸렸다. 코드 정리하면서 코딩하기도 했고, Dijkstra도 다시 상기시켜서 이해하는 데 시간이 좀 걸렸다.(설명이 쉽게 되어 있는 포스트가 있어 도움이 많이 됐다.) 전공 공부를 좀 열심히 해야 할 듯…

package practice;
 
import java.io.*;
import java.util.*;
 
/**
 * 미로를 탈출하는 데, 출발 지점에서 도착 지점까지 최소한의 벽만 깨고 도달하는 것이 목적.
 
 * 출발 지점 -> 도착 지점까지 깨야 하는 벽의 최소 갯수를 구하라
 
 * 
 
 * Input 파일의 내용
 
 * 첫 라인 : 행 열
 
 * 2~(행+1) 라인 : 열 만큼의 s | . | # | e
 
 * s가 시작 위치, e 가 끝 위치
 
 * .은 지나갈 수 있는 길, #는 벽
 
 * 
 
 * Input 파일의 내용 예제
 
 * 5 6
 
 * s.#...
 
 * ..####
 
 * #.#..#
 
 * ..###.
 
 * ###..e
 
 * 
 
 * 예제에 대한 답 : 2
 
 */
public class PracticeDijkstra {
	/** 이어지지 않은 거리(무게) */
	public static final int UNTOUCHABLE_DISTANCE = 99;
 
	/** 입력 파일명 */
	public static final String INPUT_FILE_NAME = "input.txt";
 
	/** 미로의 가로 */
	private int width = 0;
 
	/** 미로의 세로 */
	private int height = 0;
 
	/** 미로의 전체 칸 갯수 */
	private int nodeCount = 0;
 
	/** 미로 시작 지점(칸 위치 - 1차원으로 계산) */
	private int startIdx = 0;
 
	/** 미로 끝 지점(칸 위치 - 1차원으로 계산) */
	private int endIdx = 0;
 
	/** 인접 행렬 */
	private int [][] adjacentMatrix = null;
 
	/** 작업된 집합 */
	private ArrayList proceedList = null;
 
	/** 작업 대기 집합 */
	private ArrayList readyList = null;
 
	public PracticeDijkstra() {
		initValues();
	}
 
	private void initValues() {
		// Initialize
		adjacentMatrix = null;
		proceedList = null;
		readyList = null;
 
		width = 0;
		height = 0;
		nodeCount = 0;
 
		startIdx = 0;
		endIdx = 0;
	}
 
	private boolean initData(String filePath) throws FileNotFoundException, IOException, NumberFormatException {
		char [][] dataMatrix = loadDataMatrix(filePath);
 
		if( null == dataMatrix )
			return false;
 
		// 인접 행렬 만들기
		makeAdjacentMatrix(dataMatrix);
 
		return true;
	}
 
	private void makeAdjacentMatrix(char [][] dataMatrix) {
		int height = dataMatrix.length;
		int width = dataMatrix[0].length;
 
		nodeCount = height * width;
 
		adjacentMatrix = new int[nodeCount][nodeCount];
 
		for( int i = 0 ; i < height ; i++ ) {
			for( int j = 0 ; j < width ; j++ ) {
				int idx = (i * width) + j;				// current node's number(index)
 
				// Mark All Node is Untouchable Distance
				for( int k = 0 ; k < height * width ; k++ )
					adjacentMatrix[idx][k] = UNTOUCHABLE_DISTANCE;
 
				// 4-way check, including myself
				adjacentMatrix[idx][idx] = 0;
 
				int checkIdx = 0;			
 
				// left
				if( j > 0 ) {
					checkIdx = (i * width) + j - 1;
 
					if( dataMatrix[i][j-1] == '#' )
						adjacentMatrix[idx][checkIdx] = 1;
					else
						adjacentMatrix[idx][checkIdx] = 0;
				}
 
				// right
				if( j + 1 < width ) {
					checkIdx = (i * width) + j + 1;
 
					if( dataMatrix[i][j+1] == '#' )
						adjacentMatrix[idx][checkIdx] = 1;
					else
						adjacentMatrix[idx][checkIdx] = 0;
				}
 
				// up
				if( i > 0 ) {
					checkIdx = ( (i - 1) * width) + j;
 
					if( dataMatrix[i - 1][j] == '#' )
						adjacentMatrix[idx][checkIdx] = 1;
					else
						adjacentMatrix[idx][checkIdx] = 0;
				}
 
				// down
				if( i + 1 < height ) {
					checkIdx = ( (i + 1) * width) + j;
 
					if( dataMatrix[i + 1][j] == '#' )
						adjacentMatrix[idx][checkIdx] = 1;
					else
						adjacentMatrix[idx][checkIdx] = 0;
				}
			}
		}
 
		// test
		//printMatrix(adjacentMatrix);
	}
 
	private char[][] loadDataMatrix(String filePath) throws FileNotFoundException, IOException, NumberFormatException {
		BufferedReader br = new BufferedReader( new InputStreamReader( new FileInputStream(filePath)) 	);
 
		String dataSizeLine = br.readLine();
 
		if( null == dataSizeLine ) {
			System.err.println("파일 내용이 없습니다.");
			return null;
		}
 
		StringTokenizer st = new StringTokenizer(dataSizeLine, " ");
 
		if( 2 != st.countTokens() ) {
			System.err.println("파일 첫 라인의 형식이 맞지 않습니다.(파일의 첫 라인은 '행 열' 만 적혀 있어야 함)");
			return null;
		}
 
		height = Integer.parseInt( st.nextToken() );
		width = Integer.parseInt( st.nextToken() );
 
		if( 0 == height || 0 == width ) {
			System.err.println("행과 열은 1 이상이어야 합니다.");
			return null;
		}			
 
		st = null;
 
		char [][] dataMatrix = new char[height][width];
 
		for( int i = 0 ; i < height ; i++ )
		{
			String line = br.readLine();
 
			if( null == line ) {
				System.err.println("라인 수가 부족합니다.");
				return null;
			}
 
			for( int j = 0 ; j < width ; j++ )
			{
				char ch = line.charAt(j);
 
				if( ch != '.' && ch != '#' && ch != 's' && ch != 'e' ) {
					System.err.println("데이터가 올바르게 입력되지 않았습니다.");
					return null;
				}
 
				if( 's' == ch ) {
					startIdx = (i * width) + j;			// write start node's index
					ch = '.';
				} else if( 'e' == ch ) {
					endIdx = (i * width) + j;			// write end node's index
					ch = '.';
				}
 
				dataMatrix[i][j] = ch;
			}
		}
 
		br.close();
		br = null;
		// dataMatrix Load Complete
 
		// test
		//printMatrix(dataMatrix);
 
		return dataMatrix;
	}
 
	private void printMatrix(int [][] matrix) {
		int height = matrix.length;
 
		if( height <= 0 )
			return;
 
		int width = matrix[0].length;
 
		if( width <= 0 )
			return;
 
		for( int i = 0 ; i < height ; i++ ) {
			for( int j = 0 ; j < width ; j++ ) {
				System.out.print(matrix[i][j] + " ");
			}
 
			System.out.println();
		}
	}
 
	private void printMatrix(char [][] matrix) {
		int height = matrix.length;
 
		if( height <= 0 )
			return;
 
		int width = matrix[0].length;
 
		if( width <= 0 )
			return;
 
		for( int i = 0 ; i < height ; i++ ) {
			for( int j = 0 ; j < width ; j++ ) {
				System.out.print(matrix[i][j] + " ");
			}
 
			System.out.println();
		}
	}
 
	public int calculateShortestPath(String filePath) throws FileNotFoundException,
		IOException, NumberFormatException {
		initValues();
 
		if( !initData(filePath) )
			return UNTOUCHABLE_DISTANCE;
 
		// Dijkstra
		proceedList = new ArrayList();
		readyList = new ArrayList();
 
		// A - Start
		proceedList.add(startIdx);
		for( int i = 0 ; i < nodeCount ; i++ ) {
 
			if( i != startIdx )
				readyList.add(i);
		}
 
		int weightList[] = adjacentMatrix[startIdx].clone();
		int proceedTotalWeight = 0;
		int proceedLastIdx = startIdx;
 
		while( readyList.size() > 0 ) {
			int minIdx = 0;
			int minVal = UNTOUCHABLE_DISTANCE;
			proceedLastIdx = proceedList.get( proceedList.size() - 1 );
 
			Iterator iterReady = readyList.iterator();
 
			while( iterReady.hasNext() ) {
				int readyIdx = iterReady.next().intValue();
 
				int newWeight = proceedTotalWeight + adjacentMatrix[proceedLastIdx][readyIdx];
				if( adjacentMatrix[startIdx][readyIdx] > newWeight ) {
					adjacentMatrix[startIdx][readyIdx] = newWeight;
				}
 
				if( minVal > adjacentMatrix[startIdx][readyIdx] ) {
					minIdx = readyIdx;
					minVal = adjacentMatrix[startIdx][readyIdx];
				}
			}
 
			// minIdx를 readyList에서 뺌
			readyList.remove(new Integer(minIdx));
			proceedList.add(minIdx);
			proceedTotalWeight = minVal;
			proceedLastIdx = minIdx;
		}
 
		return adjacentMatrix[startIdx][endIdx];
	}
 
	public static void main(String[] args) {
		// TODO Auto-generated method stub
 
		PracticeDijkstra dijkstra = new PracticeDijkstra();
 
		int shortestWeight = PracticeDijkstra.UNTOUCHABLE_DISTANCE;
 
		try {
			shortestWeight = dijkstra.calculateShortestPath(INPUT_FILE_NAME);
 
			if( UNTOUCHABLE_DISTANCE == shortestWeight ) {
				System.out.println("Not connected");
			} else 	{
				System.out.println("Shortest path's weight : " + shortestWeight);
			}
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (NumberFormatException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
 
	}
 
}