순열
$ _nP_r : \frac{n!}{(n-r)!} $
서로 다른 원소를 가진 집합에서 대상들을 선택하여 순서 있게 배열하는 것
설계
단순히 $ _nP_r $ 을 구하고 싶다면 위의 공식을 이용해서 코드를 짜면 된다.
다만 유의할 점은 팩토리얼은 n이 조금만 커져도 수가 매우 커지기 때문에 위의 식 그대로 보다는 정리된 식을 사용하는 것이 효율적이다.
long long permutation (int n, int r) {
long long result = 1;
for (int i = n; i > n-r; i--) {
result *= i;
}
return result;
}
혹은 재귀로 나타낼 수도 있다.
long long permutation (int n, int r) {
if (r == 0) return 1;
else return n * permutation(n-1, r-1);
}
$ _nP_r $의 모든 경우의 수를 출력하고 싶은 경우- backtracking
#include <iostream>
#include <vector>
using namespace std;
int n, r;
vector <int> list;
int visit[100];
void permutation(int depth) {
if (depth == r) {
for (int i = 0; i < r; i++)
cout << list[i] << " ";
cout << "\n";
return;
}
for (int i = 1; i <= n; i++) {
if (!visit[i]) {
list.push_back(i);
visit[i] = 1;
permutation(depth+1);
list.pop_back();
visit[i] = 0;
}
}
}
int main() {
cin >> n >> r;
permutation(0);
return 0;
}
중복순열
$ _n\Pi_r: n^r $
long long permutation_dup (int n, int r) {
return pow(n, r);
}
$ _n\Pi_r $의 모든 경우의 수를 출력하고 싶은 경우- backtracking
#include <iostream>
#include <vector>
using namespace std;
int n, r;
vector <int> list;
void permutation_dup(int depth) {
if (depth == r) {
for (int i = 0; i < r; i++)
cout << list[i] << " ";
cout << "\n";
return;
}
for (int i = 1; i <= n; i++) {
list.push_back(i);
permutation_dup(depth+1);
list.pop_back();
}
}
int main() {
cin >> n >> r;
permutation_dup(0);
return 0;
}
조합
$ _nC_r: \frac{n!}{r!(n-r)!} $
서로 다른 원소를 가진 집합에서 원소들을 택하여 만든 부분집합
long long combination (int n, int r) {
int nn = 1, rr = 1;
for (int i = n; i > r; i--) {
nn *= i;
rr *= i-r;
}
return nn/rr;
}
재귀로 구하는 경우 순열에서 구한 값을 $ (n-r)! $ 로 나누어주면 된다.
long long combination (int n, int r) {
long long c = 1;
for (int i = 1; i <= r; i++)
c *= i;
return permutation(n, r) / c;
}
$ _nC_r $의 모든 경우의 수를 출력하고 싶은 경우 - backtracking
#include <iostream>
#include <vector>
using namespace std;
int n, r;
vector <int> list;
void combination(int depth, int index) {
if (depth == r) {
for (int i = 0; i < r; i++)
cout << list[i] << " ";
cout << "\n";
return;
}
for (int i = index; i <= n; i++) {
list.push_back(i);
combination(depth+1, i+1);
list.pop_back();
}
}
int main() {
cin >> n >> r;
combination(0, 1);
return 0;
}
중복조합
$ _nH_r = _{n+(r-1)}C_r $
long long combination_dup (int n, int r) {
int nn = 1, rr = 1;
n = n+r-1;
for (int i = n; i > r; i--) {
nn *= i;
rr *= i-r;
}
return nn/rr;
}
중복조합의 공식 유도
중복조합은 r개의 원소들을 순서에 상관없이 나열하는 것이므로, r개의 빈칸에 중복을 허용하여 n개의 원소를 넣는 경우의 수를 계산하는 문제와 같다. 여기에 n 가지의 경우로 구분할 수 있는 원소들을 순서에 상관없이 구분해야 하므로, n-1 개의 칸막이를 두고 n 가지 경우를 임의의 순서로 배열한다고 할 수 있다.
예를 들어 칸막이 기호를 /로 나타낸다면, 원소 A, B, C를 중복하여 5개를 뽑는 경우 중 " A A A B C "는 " A A A / B / C ", " B B B C C "는 " / B B B / C C "로 구분하는 것이다.
즉, 중복조합은 r개의 빈칸과 칸막이의 수 n-1개를 합한 r+n-1개의 빈칸에 칸막이가 들어갈 n-1개의 칸을 선택하면 된다. 결국 중복조합은 $ _{n+r−1}C_{n−1} $ 이 된다. 따라서 $_nC_r = _nC_{n−r}$이므로 $ _nH_r = _{n+r−1}C_r $ 이 된다.
$ _nH_r $의 모든 경우의 수를 출력하고 싶은 경우 - backtracking
#include <iostream>
#include <vector>
using namespace std;
int n, r;
vector <int> list;
void combination_dup(int depth, int index) {
if (depth == r) {
for (int i = 0; i < r; i++)
cout << list[i] << " ";
cout << "\n";
return;
}
for (int i = index; i <= n; i++) {
list.push_back(i);
combination_dup(depth+1, i);
list.pop_back();
}
}
int main() {
cin >> n >> r;
combination_dup(0, 1);
return 0;
}
'Algorithm > 수학' 카테고리의 다른 글
나머지 연산 (Modulo Operation) (0) | 2021.08.12 |
---|---|
이항계수 알고리즘 (0) | 2021.07.29 |
소수 판별 (0) | 2021.07.22 |
최대공약수, 최소공배수, 소인수분해 (0) | 2021.07.05 |