알고리즘-이진탐색
제비 뽑기
어느 날 친구가 봉지를 들고 와 당신에게 게임을 제안했습니다. 봉지에는 숫자가 쓰여 있는 n장의 종이가 들어 있습니다. 당신은 봉지에서 종이를 한 장 뽑고, 숫자를 확인한 후 다시 봉지에 넣는 동작을 4번 반복하여, 그 숫자의 합이 m이면 당신의 승리, 그렇지 않으면 친구가 승리하게 됩니다. 당신은 이 게임을 몇 번이나 해 보았지만 한번도 이기지 못했습니다. 화가 난 당신은 봉지를 찢어 보든 종이를 꺼낸 후 정말 이길 수 없었는지를 조사했습니다. 종이에 쓰여 있는 숫자가 k1, k2, … , kn 일 경우 합이 m 이 되는 경우가 있는 지를 조사하고, 방법이 있다면 Yes, 없다면 No 를 출력하는 프로그래밍을 장성하세요.
요약
n개의 숫자가 적힌 종이가 주머니에 들어 있고, 손을 넣어서 임의의 종이를 꺼내서 숫자를 확인한 뒤 다시 넣고 또 거내는 것을 4번 반복하여 나온 숫자들의 합이 m인지를 확인 하는 것.
입력 예)
n = 3 : 종이의 개수
m = 10 : 꺼낸 종이에 씌여진 숫자의 합
k = {1, 3, 5} : 종이에 씌여진 숫자
출력 :
Yes ( 예를 들어 1, 1, 3, 5 가 나오면 합이 10이 됩니다.)
class Pick { public void solve() { int[] k = {1, 3, 5}; int n = k.length; int m = 10; // 합이 m 이 되는 것을 찾으면 true로 변경한다. boolean found = false; // 4번을 뽑는데, 뽑은 종이를 다시 넣으므로 같은 종이를 다시 뽑을 수 있다. // 그래서 모든 뽑는 방법을 조사한다. for (int a = 0; a < n; a++) { for (int b = 0; b < n; b++) { for (int c = 0; c < n; c++) { for (int d = 0; d < n; d++) { if (k[a] + k[b] + k[c] + k[d] == m) { found = true; break; } } } } } if (found) System.out.println("Yes"); else System.out.println("No"); } }
문제가 해결되었다.
그런데 문제가 있다.
문제가 해결되었다면서 문제가 있다니?? 무슨 말인지…
n의 갯수가 커지면 for문을 어마어마하게 돌아야 한다. 이를 빅오 표기법으로 하면 O(n^4) 이다. (빅오 표기법은 간략히 설명하면 최악의 경우 얼마나 걸리는지를 수식으로 나타난 것이다.) 현재는 n이 3이기 때문에 81번만 for문을 돌지만 만약 10이라면 10000번 100 이라면 1억번, 1000이라면 10^12 번 돌아야 한다.
이렇게 되면 속도가 굉장히 오래 걸리게 된다.
이 것을 개선하기 위해서
이진탐색(binary search)
를 이용하겠다.m = k[a]+k[b]+k[c]+k[d] 이니까
k[d]=m-k[a]-k[b]-k[c] 와 같으므로 이 값을 이진탐색을 통해서 찾는 것이다.
class Pick2 { static int[] k = {1, 3, 5}; static int n = k.length; static int m = 10; public void solve() { // 합이 m 이 되는 것을 찾으면 true로 변경한다. boolean found = false; Arrays.sort(k); // 정렬은 O(n log n)의 시간복잡도를 가진다. // 1개의 for문이 제거되었다. for (int a = 0; a < n; a++) { for (int b = 0; b < n; b++) { for (int c = 0; c < n; c++) { // 제거 for (int d = 0; d < n; d++) { if (binary_search(m - k[a] - k[b] - k[c])) { found = true; break; } // } } } } if (found) System.out.println("Yes"); else System.out.println("No"); } private boolean binary_search(int x) { int startIndex = 0; int lastIndex = n; while (lastIndex - startIndex >= 1) { // 이진트리는 배열이 정렬되었다는 가정하에 동작한다. // 원하는 값이 있고, 배열의 중간에서 제일 먼저 시작하여, // 찾는 값이 더 큰지 작은지를 비교하여 절반씩 범위를 줄여 나가는 것이다. int centerIndex = (startIndex + lastIndex) / 2; // 가운데 인덱스를 찾는다. if (k[centerIndex] == x) return true; // 찾았다면 true if (k[centerIndex] < x) startIndex = centerIndex + 1; // 찾는 값 x가 더 크다면 시작인덱스를 조절한다. else lastIndex = centerIndex; // 찾는 값 x가 더 작다면 끝 인덱스를 조절한다. } // 계속 돌았지만 못 찾았다면 값이 없는 것이다. return false; } }
이렇게 하면 O(n^3 log n) - (정렬하는데 시간이 추가되었다.) 이 된다.
이를 더 개선해 보자.
2중 for문을 2번 사용하면 O(n^2 log n)이 된다.
class Pick3 { int[] k = {1, 3, 5}; int n = k.length; int m = 10; int[] preCalc = new int[n * n]; public void solve() { // 합이 m 이 되는 것을 찾으면 true로 변경한다. boolean found = false; // O(n^2) for (int c = 0; c < n; c++) { for (int d = 0; d < n; d++) { preCalc[c * n + d] = k[c] * k[d]; } } Arrays.sort(preCalc); // 정렬은 O(n log n)의 시간복잡도를 가진다. // 2개의 for문이 제거되었다. for (int a = 0; a < n; a++) { for (int b = 0; b < n; b++) { // 제거 for (int c = 0; c < n; c++) { // 제거 for (int d = 0; d < n; d++) { if (binary_search(m - k[a] - k[b])) { found = true; break; } // } // } } } if (found) System.out.println("Yes"); else System.out.println("No"); } private boolean binary_search(int x) { int startIndex = 0; int lastIndex = n; while (lastIndex - startIndex >= 1) { // 이진트리는 배열이 정렬되었다는 가정하에 동작한다. // 원하는 값이 있고, 배열의 중간에서 제일 먼저 시작하여, // 찾는 값이 더 큰지 작은지를 비교하여 절반씩 범위를 줄여 나가는 것이다. int centerIndex = (startIndex + lastIndex) / 2; // 가운데 인덱스를 찾는다. if (preCalc[centerIndex] == x) return true; // 찾았다면 true if (preCalc[centerIndex] < x) startIndex = centerIndex + 1; // 찾는 값 x가 더 크다면 시작인덱스를 조절한다. else lastIndex = centerIndex; // 찾는 값 x가 더 작다면 끝 인덱스를 조절한다. } // 계속 돌았지만 못 찾았다면 값이 없는 것이다. return false; } }
만약 n이 1000 이어도 10^6 번만 돌면 된다.
알고리즘 공부의 필요성은 효율적으로 컴퓨터를 사용하기 위함이다.
동일한 n 값이 있을 때 이를 개선하면 더욱더 좋은 성능을 낼 수 있다. (참고로 컴퓨터의 성능은 2가지 기준이 있는데, 응답속도와 처리량이다.)
예제 출처) 프로그래밍 콘테스트 챌린징(http://www.roadbook.co.kr/49)
'자바(Java) > 알고리즘-프로그래밍 콘테스트 챌린징' 카테고리의 다른 글
동적 설계법 - 값을 기억해서 재활용한다. -2 메모화 테이블 (0) | 2016.10.11 |
---|---|
동적 설계법 - 값을 기억해서 재활용한다. -1 (0) | 2016.10.11 |
알고리즘 (0) | 2016.10.06 |
댓글