2018-12-17 브루트포스 알고리즘 공부하기
브루트포스
-
브루트포스 : 모든 경우의 수를 다 해보는 것이다. 이때, 경우의수를 다 해보는데 걸리는 시간이 문제의 시간제한(보통 1,2초)를 넘지 않아야 한다.
- 3가지 단계로 생각해볼 수 있다.
- 가능한 경우의 수를 계산(직접 계산을 해야한다. 손으로 직접? 대부분은 제곱정도로)
- 가능한 모든 방법을 다 만들어본다(코딩을 사용, 하나도 빠지면 안된다. 대표적으로 그냥 다 해보는 방법을 사용 : for문, 순열, 재귀호출사용, 비트마스크사용)
- 각각의 방법을 이용해 답을 구한다.
- 시간복잡도 : 2번에 걸리는 시간 * 3번에 걸리는 시간
일곱난쟁이
- 9명의 난쟁이 중에 7명의 난쟁이 찾기
- 일곱난쟁이의 키의 합은 100이다.
- 9중에 7명을 고른다. => 2명을 고른다. 9C2 = 36가지
- 9명중에 7명을 고르는것은 9명중에 2명을 고르는 것과 같다. 난쟁이의 수를 N이라고했을때, 2명을 고르는 경우의 수 N^2라고 할 수 있다.
- 나머지 난쟁이의 키의 합을 고르는 시간 복잡도 : O(N)
- 방법의개수:O(N^2) * 값O(N) = O(N^3)
날짜계산
- 준규가 사는 나라는 E S M이라는 연도를 사용한다.
- 1<=E<=15, 1<=S<=28, 1<=M<=19
- E S M
- 경우의수 151819 = 7980
N중 for문
- N개 중 일부를 선택해야 하는 경우에 많이 사용
- 재귀 호출이나 비트마스크를 사용하면 더 간결하고 보기 쉬운 코드를 작성할 수 있기때문에, 사용할 일이 거의 없다.
1 2 3 더하기
-
정수 n을 1,2,3의 합으로 나타내는 방법의 수를 구하는 문제
- n = 4
- 1+1+1+1
- 1+1+2
- 1+2+1
- 2+1+1
- 2+2
- 1+3
-
3+1
- n이 10보다 작거나 같기 때문에 최대 10개 이하로 표현 가능
- 2중 for문을 넘어가면 보통 재귀함수를 사용한다. 폭이 넓어지고 실수할 확률이 증가해서.
순열
- 1~N까지 이루어진 수열
- 크기는 항상 N이 되어야 하고, 겹치는 숫자가 존재하지 않음
- 순열을 이용해서 문제를 풀면, 순서가 매우 중요하다.
- 크기가 N인 순열은 N!개 존재한다.
- 순열을 만든다. => 첫순열 : 오름차순, 마지막순열 : 내림차순
다음순열(Next Permutation)
- 순열을 사전순으로 나열했을 때, 사전순으로 다음에 오는 순열과 이전에 오는 순열을 찾는 방법
- A[i-1] < A[i]
- j>=i 이면서 A[j]>A[i-1]을 만족하는 가장 큰 j를 찾는다
- A[i-1]과 A[j]를 swap한다
- A[i]부터 순열을 뒤집는다.
bool next_permutation(int *a, int n) {
int i = n-1;
while (i>0 && a[i-1] >= a[i]) i -= 1;
if (i <= 0) return false; //마지막 순열
int j = n-1;
while(a[j] <= a[i-1]) j -= 1;
swap(a[i-1], a[j]);
j = n-1;
while (i<j) {
swap(a[i], a[j]);
i += 1;
j -= 1;
}
return true;
}
이전순열
bool next_permutation(int *a, int n) {
int i = n-1;
while (i>0 && a[i-1] <= a[i]) i -= 1;
if (i <= 0) return false; //마지막 순열
int j = n-1;
while(a[j] >= a[i-1]) j -= 1;
swap(a[i-1], a[j]);
j = n-1;
while (i<j) {
swap(a[i], a[j]);
i += 1;
j -= 1;
}
return true;
}
모든순열
- 첫순열을 만든다. 1…N
- 다음순열
- 다음순열….
- 다음순열이 존재하지 않을때까지.
- do-while은 모든순열을 구할때만 사용을 한다. 다음순열을 만들어 주기때문에 사용.
팩토리얼
- 모든순서를 만드는데 걸리는 시간 : N!
- 모든 순열을 만드는데 걸리는 시간이 O(N!)? 아니다. 순열의 갯수가 총 N!가 있고. 다음수열을 구하는것{O(N)}을 N!이 걸리므로 O(N*N!).
- 10정도까지만 구할 수 있다. 10팩토리얼이 3000만정도되서
차이를 최대로
- 수N개가 주어셨을때(3<=N<=8) 최대8의 경우의 수는 4만개.
- 배열의 크기 :N, 배열A: 순서만 바꾸는 것이 가능
-
A[0]-A[1] + A[1]-A[2] +…+ A[N-2]-A[N-1] 를 최대로 하는 문제 - N!=8!=40320
정렬한번하고..
do {
int temp = calculate(a);
ans = max(ans, temp);
} while(next_permutation(a.begin(), a.end());
외판원순회(TSP)
- 1~N번까지 도시가 있다.
- 한 도시에서 시작해 N개의 모든 도시를 거쳐 다시 원래 도시로 돌아오려고 한다.(한번갔던 도시로는 다시 갈 수 없다)
- 이때, 가장 적은 비용을 구하는 문제
- w[i][j] = i->j비용
- 2<=N<=10, 10! = 3628800*N
- 모든 경우를 다해봐도 시간 안에 나온다.
- d: [1,2,3,4,…n]
do {
bool ok = true;
int sum = 0;
for (int i=0; i<n-1; i++) {
if (w[d[i]][d[i+1]] == 0) ok = false;
else sum += w[d[i]][d[i+1]];
}
if (ok && w[d[n-1]][d[0]] != 0) {
sum += w[d[n-1]][d[0]];
if (ans > sum) ans = sum;
}
} while (next_permutaion(d.begin(), d.end()));
- 1,2,3,4
- 2,3,4,1
- 3,4,1,2
- 4,1,2,3은 모두 같은 경우이다.
- 즉, 시작점을 1로 고정해도 된다.
do {
bool ok = true;
int sum = 0;
for (int i=0; i<n-1; i++) {
if (w[d[i]][d[i+1]] == 0) ok = false;
else sum += w[d[i]][d[i+1]];
}
if (ok && w[d[n-1]][d[0]] != 0) {
sum += w[d[n-1]][d[0]];
if (ans > sum) ans = sum;
}
} while (next_permutaion(d.begin()+1, d.end()));
// begin이 1이니까. begin+1로시작을 해서 begin을 1로 고정을 한다.
// 복잡도가 (N-1)(N-1)! = O(N!)로 줄어든다.
do {
if (d[0] != 1) break;
bool ok = true;
int sum = 0;
for (int i=0; i<n-1; i++) {
if (w[d[i]][d[i+1]] == 0) ok = false;
else sum += w[d[i]][d[i+1]];
}
if (ok && w[d[n-1]][d[0]] != 0) {
sum += w[d[n-1]][d[0]];
if (ans > sum) ans = sum;
}
} while (next_permutaion(d.begin(), d.end()));
Written on December 17, 2018