A, B, C, D, E로 이루어진 5장의 카드. 이 5장의 카드 중 3장을 선택하여 나열하려고 할 때, 다음 조건을 각각 만족하는 경우를 찾아야 함.
1. 순서를 생각하며 3장 선택하기
2. 순서를 생각하지 않고 3장 선택하기
모든 카드를 한 장씩 나열하면서 나열된 카드가 3장에 도달하면 카드의 나열을 중지함. 해당 조건을 만족하려면 다음과 같은 방법으로 경우의 수를 구함.
따라서 5X4X3=60 가지의 방법이 있음
이렇게 n개 중에서 일부만을 선택하여 나열하는 것을 순열이라고 함. 순열은 순서를 지키며 나열해야함.
예를 들어 가드를 3장 뽑을 때, [A,B,D]와 [A,D,B] 두 경우 모두 A, B 그리고 D라는 같은 가드를 3장 선택했지만, 나열하는 순서가 다르므로 서로 다른 경우로 파악해야함.
순열은 일반화 과정을 거쳐 Permutation의 약자 P로 표현함.
// 반복문 코드
public static ArrayList<String[]> permutationLoop() {
// 순열 요소가 인자로 주어질 경우, 인자 그대로 사용하면 되지만, 인자가 주어지지 않고
// 문제 안에 포함되어 있을 경우 이런 식으로 직접 적어서 사용합니다.
String[] lookup = new String[]{"A", "B", "C", "D", "E"};
ArrayList<String[]> result = new ArrayList<>();
for (int i = 0; i < lookup.length; i++) {
for (int j = 0; j < lookup.length; j++) {
for (int k = 0; k < lookup.length; k++) {
if (i == j || j == k || k == i) continue;
String[] input = new String[]{lookup[i], lookup[j], lookup[k]};
result.add(input);
}
}
}
return result;
}
result 배열 안에 순열의 경우의 수를 삽입한 뒤, 반환하는 함수
// 반복문 1개당 1개의 요소를 뽑습니다.
for (int i = 0; i < lookup.length; i++) {
String pick1 = lookup[i];
for (int j = 0; j < lookup.length; j++) {
String pick2 = lookup[j];
for (letintk = 0; k < lookup.length; k++) {
String pick3 = lookup[k];
if (i.equals(j) || j.equals(k) || k.equals(i)) continue;
result.add(new String[]{pick1, pick2, pick3});
}
}
}
결과
/*
[
[ 'A', 'B', 'C' ], [ 'A', 'B', 'D' ], [ 'A', 'B', 'E' ],
[ 'A', 'C', 'B' ], [ 'A', 'C', 'D' ], [ 'A', 'C', 'E' ],
[ 'A', 'D', 'B' ], [ 'A', 'D', 'C' ], [ 'A', 'D', 'E' ],
[ 'A', 'E', 'B' ], [ 'A', 'E', 'C' ], [ 'A', 'E', 'D' ],
[ 'B', 'A', 'C' ], [ 'B', 'A', 'D' ], [ 'B', 'A', 'E' ],
[ 'B', 'C', 'A' ], [ 'B', 'C', 'D' ], [ 'B', 'C', 'E' ],
[ 'B', 'D', 'A' ], [ 'B', 'D', 'C' ], [ 'B', 'D', 'E' ],
[ 'B', 'E', 'A' ], [ 'B', 'E', 'C' ], [ 'B', 'E', 'D' ],
[ 'C', 'A', 'B' ], [ 'C', 'A', 'D' ], [ 'C', 'A', 'E' ],
[ 'C', 'B', 'A' ], [ 'C', 'B', 'D' ], [ 'C', 'B', 'E' ],
[ 'C', 'D', 'A' ], [ 'C', 'D', 'B' ], [ 'C', 'D', 'E' ],
[ 'C', 'E', 'A' ], [ 'C', 'E', 'B' ], [ 'C', 'E', 'D' ],
[ 'D', 'A', 'B' ], [ 'D', 'A', 'C' ], [ 'D', 'A', 'E' ],
[ 'D', 'B', 'A' ], [ 'D', 'B', 'C' ], [ 'D', 'B', 'E' ],
[ 'D', 'C', 'A' ], [ 'D', 'C', 'B' ], [ 'D', 'C', 'E' ],
[ 'D', 'E', 'A' ], [ 'D', 'E', 'B' ], [ 'D', 'E', 'C' ],
[ 'E', 'A', 'B' ], [ 'E', 'A', 'C' ], [ 'E', 'A', 'D' ],
[ 'E', 'B', 'A' ], [ 'E', 'B', 'C' ], [ 'E', 'B', 'D' ],
[ 'E', 'C', 'A' ], [ 'E', 'C', 'B' ], [ 'E', 'C', 'D' ],
[ 'E', 'D', 'A' ], [ 'E', 'D', 'B' ], [ 'E', 'D', 'C' ]
]
*/
2번 조건에서 모든 경우의 수를 구할 때는 3장을 하나의 그룹으로 선택해야 함
먼저 조합은 순열과 달리 순서를 고려하지 않음. 만약 순열처럼 순서를 생각하여 경우의 수를 센다면, 조합으로써 올바르지 않음. 예를 들어 순열에서는 [A, B, C], [A, C, B], [B, A, C], [B, C, A], [C, A, B], [C, B, A]의 여섯가지는 모두 다른 경우로 취급하지만, 조합에서는 이 여섯 가지를 하나의 경우로 취급함. 다시 말해 순열에서처럼 순서를 생각하여 선택하면, 중복된 경우가 6배 발생함.
여기서 나온 여섯 가지 경우의 수는 3장의 카드를 순서를 생각하여 나열한 모든 경우의 수임.
3장의 카드를 순열 공식에 적용한 결과가 3!/(3-3)! = (3X2X1) / 1 = 6 임. 순서를 생각하느라 중복된 부분이 발생한 순열의 모든 가지수를, 중복된 6가지로 나누어 주면 조합의 모든 경우의 수를 얻을 수 있음.
따라서 (5X4X3X2X1) / ((3X2X1) X (2X1)) = 10 임.
조합을 일반화 과정을 가쳐, Combination의 약자 C로 표현함.
조합의 모든 경우의 수
// 반복문 코드
public static ArrayList<String[]> combinationLoop() {
// 조합 요소가 인자로 주어질 경우, 인자 그대로 사용하면 되지만, 인자가 주어지지 않고
// 문제 안에 포함되어 있을 경우 이런 식으로 직접 적어서 사용합니다.
String[] lookup = new String[]{"A", "B", "C", "D", "E"};
ArrayList<String[]> result = new ArrayList<>();
for(int i = 0; i < lookup.length; i++) {
for(int j = i + 1; j < lookup.length; j++) {
for(int k = j + 1; k < lookup.length; k++) {
String[] input = new String[]{lookup[i], lookup[j], lookup[k]};
result.add(input);
}
}
}
return result;
}
순열과 마찬가지로 result 배열 안에 순열의 경우의 수를 삽입한 뒤, 반환하는 함수
결과
/*
[
[ 'A', 'B', 'C' ], [ 'A', 'B', 'D' ], [ 'A', 'B', 'E' ],
[ 'A', 'C', 'D' ], [ 'A', 'C', 'E' ], [ 'A', 'D', 'E' ],
[ 'B', 'C', 'D' ], [ 'B', 'C', 'E' ], [ 'B', 'D', 'E' ],
[ 'C', 'D', 'E' ]
]
*/
한계점
요소.length
를 사용하여 대응할 수 있지만, 뽑아야 되는 개수도 변수로 받게 된다면 몇 개의 반복문을 설정해야하는지, 설정은 어떻게 하는지 굉장히 까다로워짐