반응형
완전 검색
순열(Permutation)
- 서로 다른 n개 중 r개를 선택하는 과정에서 순서가 의미가 있는 경우 - nPr
- 예 ) 5개의 도시 중 출발지와 도착지가 같은 최소 비용 거리
반복문
// {1, 2, 3}을 포함한 모든 순열 생성 : 3P3
// 동작 과정 1. 1~3 수 선택 시도, 2. 중복 수 필터링, 3. 다음 자릿수 선택 - 선택 이후 1,2,3 반복
for i from 1 to 3 // 처음 수 선택
for j from 1 to 3 // 두번째 수 선택
if j != i then // 첫번째와 두번째가 다르다면
for k from 1 to 3 // 세번 째 수 선택
if k != i and k != j then // 세번째 수가 첫번째, 두번째와 다른 경우
print i,j,k // 세 수 선택
end if
end for
end if
end for
end for
- 단점 : r중 반복문을 사용해야하므로 가변적인 순열에 대응할 수 없다.
재귀
//numbers[] : 순열 저장 배열
//isSelected[] : 인덱스에 해당하는 숫자가 사용 중인지 저장하는 배열
perm(cnt) // cnt : 현재까지 뽑은 순열 수의 개수
if cnt == 3 // 기저 조건
// 리턴
else
for i from 1 to 3
if isSelected[i] == true then continue // 기존 수 중복 체크
numbers[cnt] <- i
isSelected[i] <- true
perm(cnt+1) // 다음 수 선택
isSelected[i] <- false
end for
- 재귀를 사용하면 스택에 r개만큼 쌓아서 가변적 순열처리가 가능하다.
import java.util.Arrays;
import java.util.Scanner;
public class PermutationTest {
static int N, R;
static int[] input, numbers; // input : 입력수 배열, numbers : 선택수 배열
static boolean[] isSelected;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
N = sc.nextInt();
R = sc.nextInt();
input = new int[N];
numbers = new int[R];
isSelected = new boolean[N];
for (int i = 0; i < N; i++) {
input[i] = sc.nextInt();
}
permutation(0);
}
// 현재 자리에 수 뽑기
public static void permutation(int cnt) {
if (cnt == R) {
System.out.println(Arrays.toString(numbers));
return;
}
// 입력받은 모든 수를 현재 자리에 넣어보기
for (int i = 0; i < N; i++) {
// 기존자리의 수들과 중복되는지 체크
if (isSelected[i]) continue;
numbers[cnt] = input[i];
isSelected[i] = true;
// 다음 수 뽑으러 가기
permutation(cnt+1);
isSelected[i] = false;
}
}
}
비트마스킹 - 순열
nPn -> N개의 원소로 만들 수 있는 모든 순열 생성
input[] : 숫자 배열
numbers[] : 순열 저장 배열
perm(cnt, flag) // cnt : 현재까지 뽑은 순열 원소의 개수, flag : 선택된 원소에 대한 비트정보를 표현하는 정수
if cnt == N
// 리턴
else
for i from 0 to N-1
if (flag & 1 << i) != 0 then continue
numbers[cnt] <- input[i]
perm(cnt+1, flag | 1 << i)
end for
end perm()
import java.util.Arrays;
import java.util.Scanner;
public class PermutationTest {
static int N, R;
static int[] input, numbers; // input : 입력수 배열, numbers : 선택수 배열
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
N = sc.nextInt();
R = sc.nextInt();
input = new int[N];
numbers = new int[R];
for (int i = 0; i < N; i++) {
input[i] = sc.nextInt();
}
permutation(0,0);
}
// cnt : 직전까지 뽑은 수 갯수, flag : 뽑힌 수들에 대한 플래그 비트열
public static void permutation(int cnt, int flag) {
if (cnt == R) {
System.out.println(Arrays.toString(numbers));
return;
}
for (int i = 0; i < N; i++) {
if ((flag & 1 << i) != 0) continue;
numbers[cnt] = input[i];
permutation(cnt+1, flag | 1 << i);
}
}
}
NextPermutation
import java.util.Arrays;
import java.util.Scanner;
public class NextPermutationTest {
static int N, input[];
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
N = sc.nextInt();
input = new int[N];
for (int i=0; i<N; i++) {
input[i] = sc.nextInt();
}
Arrays.sort(input);
do {
System.out.println(Arrays.toString(input));
}while (np());
}
private static boolean np() {
// 1. 교환위치 찾기
int i = N-1;
while(i>0 && input[i-1] >= input[i]) --i;
if (i==0) return false;
// 2. 교환위치에 교환할 값 찾기
int j = N-1;
while(input[i-1] >= input[j]) --j;
// 3. 교환위치와 교환할 값 교환하기
swap(i-1,j);
// 4. 교환위치 뒤(꼭대기)부터 맨 뒤까지 만들 수 있는 가장 작은 순열 생성 (오름차순 정렬)
int k = N-1;
while (i < k) {
swap(i++, k--);
}
return true;
}
public static void swap(int i, int j) {
int tmp = input[i];
input[i] = input[j];
input[j] = tmp;
}
}
조합
- 서로 다른 n개 중 r개를 선택하는 과정에서 순서가 의미없는 경우
- 5개의 도시 중 3개를 선택할 때 가장 숙박비가 비싼 3개의 도시
반복문
// 4C3 조합 - 순서가 의미가 없으므로 다음 수는 전수 +1
for i from 1 to 4
for j from i+1 to 4
for k from j+1 to 4
print i,j,k
end for
end for
end for
재귀
comb(cnt, start)
if cnt == r
// 리턴
else
for i from start to n-1
numbers[cnt] <- input[i];
comb(cnt+1,i+1)
end for
end comb()
- 다음 수는 전수+1부터 시작하게 하며, 순서가 의미가 없으므로 중복체크를 하지 않아도 된다.
import java.util.Arrays;
import java.util.Scanner;
public class CombinationTest {
static int N,R;
static int[] input, numbers;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
N = sc.nextInt();
R = sc.nextInt();
input = new int[N];
numbers = new int[R];
for (int i = 0; i < N; i++) {
input[i] = sc.nextInt();
}
combination(0, 0);
}
public static void combination(int cnt, int start) {
if (cnt == R) {
System.out.println(Arrays.toString(numbers));
return;
}
for (int i = start; i < N; i++) {
numbers[cnt] = input[i];
combination(cnt+1, i); // 다음 자리는 현재 뽑은 i의 다음수부터 시작하도록 전달
}
}
}
NextPermutation
import java.util.Scanner;
public class CombinationNPTest {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int N = sc.nextInt();
int R = sc.nextInt();
int[] input = new int[N];
for (int i=0; i<N; i++) {
input[i] = sc.nextInt();
}
int[] p = new int[N];
// p배열에 0보다 큰 값으로 R개를 맨뒤부터 채운다.
// 4C2 0011
int cnt = 0;
while(++cnt <= R) p[N-cnt] = 1;
do {
for (int i = 0; i < N; i++) {
if (p[i] == 1) {
System.out.print(input[i] +" ");
}
}
System.out.println();
} while(np(p));
}
private static boolean np(int[] p) {
int N = p.length;
// 1. 교환위치 찾기
int i = N-1;
while(i>0 && p[i-1] >= p[i]) --i;
if (i==0) return false;
// 2. 교환위치에 교환할 값 찾기
int j = N-1;
while(p[i-1] >= p[j]) --j;
// 3. 교환위치와 교환할 값 교환하기
swap(p,i-1,j);
// 4. 교환위치 뒤(꼭대기)부터 맨 뒤까지 만들 수 있는 가장 작은 순열 생성 (오름차순 정렬)
int k = N-1;
while (i < k) {
swap(p,i++, k--);
}
return true;
}
public static void swap(int[] p ,int i, int j) {
int tmp = p[i];
p[i] = p[j];
p[j] = tmp;
}
}
중복 순열, 중복 조합 예제
import java.util.Arrays;
import java.util.Scanner;
public class DiceTest {
static int N, numbers[], totalCount;
static boolean[] isSelected;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
N = sc.nextInt(); // 던진 주사위 횟수
numbers = new int[N]; // 차례대로 던져진 주사위 눈의 수
int M = sc.nextInt();
switch (M) {
case 1 : // 주사위 던지기1 : 중복순열
dice1(0);
break;
case 2 : // 주사위 던지기2 : 순열
dice2(0, new boolean[7]);
break;
case 3 :
dice3(0,1);
break;
case 4 : // 주사위 던지기4 : 조합
dice4(0,1);
break;
default:
break;
}
System.out.println("총 경우의 수 : " + totalCount);
}
// 주사위 던지기 1 : 중복 순열
public static void dice1(int cnt) {
if(cnt == N) {
totalCount++;
System.out.println(Arrays.toString(numbers));
return;
}
for (int i = 1; i <= 6; i++) {
numbers[cnt] = i;
dice1(cnt+1);
}
}
// 순열 : nPr
public static void dice2(int cnt, boolean[] isSelected) {
if(cnt == N) {
totalCount++;
System.out.println(Arrays.toString(numbers));
return;
}
for (int i = 1; i <= 6; i++) {
if(isSelected[i]) continue;
numbers[cnt] = i;
isSelected[i] = true;
dice2(cnt + 1, isSelected);
isSelected[i] = false;
}
}
// 중복조합 : nHr -> n+r-1Cr
public static void dice3(int cnt, int start) {
if (cnt == N) {
totalCount++;
System.out.println(Arrays.toString(numbers));
return;
}
for (int i = start; i <= 6; i++) {
numbers[cnt] = i;
dice4(cnt+1, i);
}
}
// 조합 : nCr
public static void dice4(int cnt, int start) {
if(cnt == N) {
totalCount++;
System.out.println(Arrays.toString(numbers));
return;
}
for (int i = start; i <= 6; i++) {
numbers[cnt] = i;
dice4(cnt+1, i+1);
}
}
}
부분 집합
- 조합의 모든 경우의 수 (5C1, 5C2, ... , 5C5)에서 5C0 까지 포함된 경우
- 냅백 알고리즘과 같은 다수의 중요 알고리즘이 최적의 부분 집합을 찾는 것
- 원소의 개수가 n개일 때 공집합을 포함한 부분 집합의 개수는 2^n 개
반복문
// 1~3 집합의 모든 부분 집합
FOR i 1 -> 0
selected[i] <- i
FOR j in 1 -> 0
selected[2] <- j
FOR k in 1 -> 0
selected[3] <- k
FOR m in 1 -> 3
if selected[i] == 1 then
print i
- 부분 집합의 원소가 몇개가 될지 모르므로 반복문으로 미리 코드를 짤 수가 없다.
재귀
input[] : 숫자 배열
isSelected[] : 부분집합에 포함 / 비포함 여부를 저장한 배열
generateSubSet(cnt) // cnt : 현재까지 처리한 원소 개수
if (cnt == N)
// 리턴
else
isSelected[cnt] <- true
generateSubSet(cnt+1) // 해당 원소를 선택한 경우
isSelected[cnt] <- false
generateSubSet(cnt+1) // 해당 원소를 선택하지 않은 경우
end generateSubSet()
- 간단하게 선택, 비선택으로 나누어 재귀를 호출한다.
import java.util.Scanner;
public class SubSetTest {
static int N, input[];
static boolean[] isSelected;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
N = sc.nextInt();
input = new int[N];
isSelected = new boolean[N];
for (int i = 0; i < N; i++) {
input[i] = sc.nextInt();
}
generateSubset(0);
}
public static void generateSubset(int cnt) { // 부분집합에 고려해야 하는 원소의 개
if (cnt == N) {
for (int i = 0; i < N; i++) {
System.out.print((isSelected[i]?input[i]:"X")+" ");
}
System.out.println();
return;
}
// 현재 원소를 선택
isSelected[cnt] = true;
generateSubset(cnt+1);
// 현재 원소를 비선택
isSelected[cnt] = false;
generateSubset(cnt+1);
}
}
반응형