일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- 쓰레드
- 멀티프로세서
- 렌더링 파이프라인
- 락
- 디자인패턴
- DirectX12
- 병행성
- DirectX 12
- Direct12
- 프로그래머스
- 알고리즘
- 다이나믹 프로그래밍
- 파일시스템 구현
- 다이나믹프로그래밍
- directx
- OS
- 자료구조
- 타입 객체
- 스케줄링
- 동적계획법
- 병행성 관련 오류
- 운영체제
- 그리디알고리즘
- I/O장치
- 그리디 알고리즘
- 백준
- 멀티쓰레드
- codility
- 영속성
- 컨디션 변수
- Today
- Total
기록공간
완전탐색 - 보글 게임 (난이도 : 하, Java) 본문
문제와 내용은 "프로그래밍 대회에서 배우는 알고리즘 문제해결 전략" 책을 참고하였습니다.
알고리즘 문제 해결 전략
프로그래밍 대회에서 배우는 알고리즘 문제 해결 전략, 구종만 지음, 인사이트, ISBN 978-89-6626-054-6 새 소식 책 소개 <알고리즘 문제 해결 전략>은 새로운 알고리즘 책입니다. 종이에 적힌 의사코드
book.algospot.com
이것을 풀기 위한 가장 간단한 방법은 완전 탐색을 이용해, 단어를 찾아낼 때까지 모든 인접한 칸을 하나씩 시도해 보는 것이다. 그중 한 칸에서라도 단어를 찾을 수 있으면 성공이고, 어느 칸을 선택하더라도 답이 없다면 실패가 된다.
문제의 분할
hasWord()가 하는 일을 가장 자연스럽게 조각내는 방법은 각 글자를 하나의 조각으로 만드는 것이다. 함수 호출 시에 단어의 시작 위치를 정해주기 때문에, 문제의 조각들 중 첫 번째 글자에 해당하는 조각을 간단하게 해결할 수 있다. 시작 위치에 쓰여 있는 글자가 단어의 첫 글자와 다르다면 곧장 false를 반환하고 종료하면 되기 때문이다. 그러고 나면 원래 단어 word에서 첫 글자를 뗀 단어 word[1...]을 격자에서 찾아야 한다. word[1...]의 시작 위치는 (y, x)와 인접한 여덟 칸 중 하나일 것이다. 따라서 여덟 경우를 모두 시도하며 답을 찾으면 된다.
기저 사례의 선택
더 이상의 탐색 없이 간단히 답을 낼 수 있는 다음 경우들은 기저 사례로 선택한다.
-
위치 (y, x)에 있는 글자가 원하는 단어의 첫 글자가 아닌 경우 항상 실패
-
(1번 경우에 해당하지 않는 경우) 원하는 단어가 한 글자인 경우 항상 성공
간결한 코드를 작성하는 유용한 팁이 있는데, 입력이 잘못되거나 범위에서 벗어난 경우도 기저 사례로 택해서 맨 처음에 처리하는 것이다. 그러면 함수를 호출하는 시점에서 이런 오류를 검사할 필요가 없다. 재귀 함수는 항상 한군데 이상에서 호출되기 때문에, 이 습관은 반복적인 코드를 제거하는 데 큰 도움이 된다. 따라서 위 두 가지 경우 외에도 현재 위치가 게임판을 벗어난 경우와, 첫 글자가 일치하지 않는 경우를 모두 기저 사례로 선택해 처리하도록 하자.
구현
import java.util.Scanner;
public class Boggle {
static final int[] dx = { -1, -1, -1, 1, 1, 1, 0, 0 };
static final int[] dy = { -1, 0, 1, -1, 0, 1, -1, 1 };
static char[][] board = new char[5][5];
// 5x5 보글 게임 판에 해당 위치에서 주어진 단어가 시작하는지를 반환
static boolean hasWord(int y, int x, String word) {
// 기저사례 1 : 시작 위치가 밖이면 무조건 실패
if(!inRange(y, x)) return false;
// 기저사례 2 : 첫 글자가 일치하지 않으면 실패
if(board[y][x] != word.charAt(0)) return false;
// 기저사례 3 : 단어 길이가 1이면 성공
if(word.length() == 1) return true;
// 인접한 여덟 칸을 검사한다.
for(int direction = 0; direction < 8; ++direction) {
int nextY = y + dy[direction];
int nextX = x + dx[direction];
// 다음 칸이 범위 안에 있는지, 첫 글자는 일치하는지 확인할 필요가 없다.
if(hasWord(nextY, nextX, word.substring(1)))
return true;
}
return false;
}
static boolean inRange(int y, int x) {
return (x >= 0 && x < 5) && (y >= 0 && y < 5);
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
// board판 입력
for(int i = 0; i < 5; ++i) {
String tmpStr = sc.next();
for(int j = 0; j < 5; ++j) {
board[i][j] = tmpStr.charAt(j);
}
}
// 확인할 단어 수 입력
int testCount = sc.nextInt();
String[] str = new String[testCount];
// 단어 입력
for(int i = 0 ; i < str.length; ++i) {
str[i] = sc.next();
}
for(int n = 0; n < str.length; ++n) {
System.out.print(str[n] + " ");
boolean isWord = false;
for(int i = 0; i < 5; ++i) {
for(int j = 0; j < 5; ++j) {
if(hasWord(i, j, str[n])) {
isWord = true;
break;
}
}
if(isWord) break;
}
if(isWord) System.out.println("YES");
else System.out.println("NO");
}
}
}
hasWord()의 처음에서 시작 위치의 범위와 첫 글자 일치 여부를 확인하고 있기 때문에 for문 안에서 별도로 확인을 하지 않아도 된다. 다음 칸의 상대 좌표 목록을 함수 내에 직접 코딩해 넣은 것이 아닌 별도의 변수로 분리해 낸 점도 참고할 만하다.
결과
시간 복잡도 분석
완전 탐색 알고리즘의 시간 복잡도를 계산하는 것은 비교적 단순하다. 완전 탐색은 가능한 답 후보들을 모두 만들어 보기 때문에, 시간 복잡도를 계산하기 위해서는 가능한 후보의 수를 전부 세어보면 된다.
마지막 글자에 도달하기 전에는 주변의 모든 칸에 대해 재귀 호출을 하게 된다. 각 칸에는 최대 여덟 개의 이웃이 있고, 탐색은 단어의 길이 N에 대해 N - 1 단계 진행된다. 따라서 검사하는 후보의 수는 최대 8^(N - 1) = O(8^N)이 되고, 이것이 알고리즘 시간 복잡도가 된다. (엄청 느리다!)
단어 길이에 따라 지수적으로 증가하기 때문에 단어의 길이가 짧은 경우에만 완전 탐색으로 해결할 수 있다.
완전 탐색 레시피
어떤 문제를 완전 탐색으로 해결하기 위해 필요한 과정은 대략 다음과 같다. 이 과정이 모든 문제에 항상 적용되는 것은 아니지만, 처음 접근할 때 대략적인 지침은 될 것이다.
-
완전 탐색은 존재하는 모든 답을 하나씩 검사하므로, 걸리는 시간은 가능한 답의 수에 비례한다. 최대 크기의 입력을 가정했을 때 답의 개수를 계산하고 이들을 모두 제한 시간 안에 생성할 수 있을지를 가늠한다. 만약 시간 안에 계산할 수 없다면 다른 설계 패러다임을 적용해야 한다.
-
가능한 모든 답의 후보를 만드는 과정을 여러 개의 선택으로 나눈다. 각 선택은 답의 후보를 만드는 과정의 한 조각이 된다.
-
그중 하나의 조각을 선택해 답의 일부를 만들고, 나머지 답을 재귀 호출을 통해 완성한다.
-
조각이 하나밖에 남지 않은 경우, 혹은 하나도 남지 않은 경우에는 답을 생성했으므로, 이것을 기저 사례로 선택해 처리한다.
'Algorithm > 예제' 카테고리의 다른 글
완전탐색 - 중첩 반복문 대체하기 (0) | 2020.08.16 |
---|