서로 다른 것들 중 몇개를 뽑아서 한줄로 나열하는 것이다.
nPr
이라고 표현한다. nPr
은 다음 식이 성립한다. nPr = n * (n-1) * (n-2) * ... * (n-r+1)
nPn
은 n!
이라고 표기하며 Factorial이라고 부른다.boolean
배열 사용)flag
변수를 사용해 이미 선택된 원소인지 확인한다. (자료형의 bit개수만큼 숫자의 사용여부를 판단할 수 있다.)and
연산과 shift
연산을 통해 flag
가 0
인지 확인해준다. (조건판단)flag
가 0
일경우 해당 원소는 이미 순열에 포함된 원소이므로 선택하지 않고 넘어간다.or
연산을 통해 기존 비트열(flag
)에 상태를 누적해준다.i - 1
)를 찾는다. (꼭대기 i
-> 가장 큰 i
)i - 1
)와 교환할 큰 값(j
) 즉, i-1
보다 큰 값을 갖는 위치를 찾는다. 이때, array[i-1] < array[i]
이기 떄문에 j
를 못찾는 경우는 없다.i - 1
, j
)을 교환한다.만약, 주어진 배열이
5 4 3 2 1
이라면0
번째 인덱스인5
가 꼭대기가 된다. 즉, 꼭대기가0
번째 인덱스가 되는 순간이 순열의 마지막 순서이다.
또한, 항상 다음순열이 되는 값의 특징은 이전 순열보다 값이 크다.
O(n!)
O(n)
n
개의 요소들에 대하여 n!
개의 순열이 존재하는데 이때 n>12
인 경우, 시간복잡도가 엄청나게 증가한다. 따라서 n
의 크기를 잘 확인해야한다.[재귀호출을 통한 순열생성 예제코드]
public class PermutationTest {
public static void main(String[] args) throws IOException {
final BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
final int numberCount = Integer.parseInt(bufferedReader.readLine());
// 전체 숫자 개수
final int permutationCount = Integer.parseInt(bufferedReader.readLine());
// 뽑아야하는 숫자
final int[] input = new int[numberCount];
final boolean[] isSelected = new boolean[numberCount];
final int[] permutation = new int[permutationCount];
final StringTokenizer stringTokenizer = new StringTokenizer(bufferedReader.readLine());
for (int i = 0; i < numberCount; i++) {
input[i] = Integer.parseInt(stringTokenizer.nextToken());
}
makePermutation(input, permutation, isSelected, 0);
}
private static void makePermutation(final int[] input, final int[] permutation, final boolean[] isSelected, final int count) {
// 해당숫자가 수열을 생성하는데 사용되었는지 확인하는 사용여부를 체크하는 배열을 사용한다.
if (count == permutation.length) {
System.out.println(Arrays.toString(permutation));
return;
}
for (int i = 0; i < input.length; i++) {
if (isSelected[i]) {
continue;
}
permutation[count] = input[i];
isSelected[i] = true;
makePermutation(input, permutation, isSelected, count + 1);
isSelected[i] = false;
}
}
}
[비트마스킹을 통한 순열생성 예제코드]
public class PermutationTest_Bitmask {
public static void main(String[] args) throws IOException {
final BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
final int numberCount = Integer.parseInt(bufferedReader.readLine());
final int permutationCount = Integer.parseInt(bufferedReader.readLine());
final int[] input = new int[numberCount];
final int[] permutation = new int[permutationCount];
final StringTokenizer stringTokenizer = new StringTokenizer(bufferedReader.readLine());
for (int i = 0; i < numberCount; i++) {
input[i] = Integer.parseInt(stringTokenizer.nextToken());
}
makePermutation(input, permutation, 0, 0);
}
private static void makePermutation(final int[] input, final int[] permutation, final int count, final int flag) {
// flag: 선택된 원소에 대한 비트정보를 표현하는 정수
부를 확인한다.
if (count == permutation.length) {
System.out.println(Arrays.toString(permutation));
return;
}
for (int i = 0; i < input.length; i++) {
// 사용여부를 체크하는 boolean 배열대신 flag변수를 사용해 비트연산을 통해 사용여
if ((flag & 1 << i) != 0) {
// 해당 원소가 이미 순열에 사용되었다면 선택하지 않고 넘어간다.
// and 연산을 통해 순열에 사용되었는지 조건판단
continue;
}
permutation[count] = input[i];
makePermutation(input, permutation, count + 1, (flag | 1 << i));
// 해당원소를 선택했다면 해당 원소의 비트를 1로 마킹하고 다음 원소를 찾는다.
// or 연산으로 기존 비트열에 상태 누적.
}
}
}
[NextPermuatation 통한 순열생성 예제코드]
public class PermutationTest_NextPer {
public static void main(String[] args) throws IOException {
final BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
final int numberCount = Integer.parseInt(bufferedReader.readLine());
final int[] input = new int[numberCount];
final StringTokenizer stringTokenizer = new StringTokenizer(bufferedReader.readLine());
for (int i = 0; i < numberCount; i++) {
input[i] = Integer.parseInt(stringTokenizer.nextToken());
}
Arrays.sort(input);
do {
System.out.println(Arrays.toString(input));
} while (makePermutation(input));
}
private static boolean makePermutation(final int[] input) {
int i = input.length - 1;
// 뒤쪽부터 탐색하며 교차점 찾기
while (i > 0 && input[i - 1] >= input[i]) {
i--;
}
// i = 0이면 순열을 생성하지 않는다.
// i = 0이라는 것은 순열이 내림차순 형태로 정렬되있다는것을 의미한다.
if (i == 0) {
return false;
}
// 뒤쪽부터 탐색하며 꼭대기 바로 앞자리의 값을 크게만들 교환할 값을 찾는다.
int j = input.length - 1;
while (input[i - 1] >= input[j]) {
j--;
}
// i-1번째 값과 j번째 값을 교환한다.
swap(input, i - 1, j);
// i 이후의 값들을 가장 작은 형태의 오름차순으로 정렬한다.
int k = input.length - 1;
while (i < k) {
swap(input, i++, k--);
}
return true;
}
private static void swap(final int[] input, final int i, final int j) {
int temp = input[i];
input[i] = input[j];
input[j] = temp;
}
}