포스트

[수학] 순열과 조합

[수학] 순열과 조합

순열(Permutation)과 조합(Combination) 정리 (시간복잡도 포함)

게임 개발, 알고리즘 문제, 수학 전반에서 자주 쓰이는 개념인 순열과 조합에 대해
개념, 수학 공식, 구현 방법, STL 활용, 중복 조합까지 정리한 문서입니다.


1. 순열과 조합의 기본 개념 비교

구분의미예시수학 기호
순열순서를 고려한 선택3명 중 2명을 뽑아 줄 세우는 경우P(n, r) 또는 nPr
조합순서를 고려하지 않는 선택3명 중 2명을 뽑는 경우C(n, r) 또는 nCr

2. 수학 공식

순열

\(P(n, r) = \frac{n!}{(n - r)!}\)

조합

\(C(n, r) = \frac{n!}{r!(n - r)!}\)


3. 팩토리얼 및 기본 구현

1
2
3
4
5
6
7
long long factorial(int n) {
    long long res = 1;
    for (int i = 2; i <= n; ++i)
        res *= i;
    return res;
}
// 시간복잡도: O(n)

순열 함수

1
2
3
4
long long permutation(int n, int r) {
    return factorial(n) / factorial(n - r);
}
// 시간복잡도: O(n)

조합 함수

1
2
3
4
long long combination(int n, int r) {
    return factorial(n) / (factorial(r) * factorial(n - r));
}
// 시간복잡도: O(n)

4. 조합의 DP(파스칼 삼각형) 구현

1
2
3
4
5
6
7
8
9
10
11
long long comb[101][101];

void buildCombinationTable(int maxN) {
    for (int n = 0; n <= maxN; ++n) {
        comb[n][0] = comb[n][n] = 1;
        for (int r = 1; r < n; ++r) {
            comb[n][r] = comb[n - 1][r - 1] + comb[n - 1][r];
        }
    }
}
// 시간복잡도: O(n^2)

5. STL로 순열 생성 – next_permutation

1
2
3
4
5
6
7
std::vector<int> v = {1, 2, 3};
std::sort(v.begin(), v.end());
do {
    for (int x : v) std::cout << x << ' ';
    std::cout << '\n';
} while (std::next_permutation(v.begin(), v.end()));
// 시간복잡도: O(n! × n)

6. STL로 조합 생성 – 마스크 + prev_permutation

1
2
3
4
5
6
7
8
9
10
std::vector<int> data = {10, 20, 30, 40, 50};
std::vector<int> mask(5, 0);
std::fill(mask.begin(), mask.begin() + 3, 1);

do {
    for (int i = 0; i < 5; ++i)
        if (mask[i]) std::cout << data[i] << ' ';
    std::cout << '\n';
} while (std::prev_permutation(mask.begin(), mask.end()));
// 시간복잡도: O(nCr × n)

prev_permutation을 사용할까?

  • 마스크 {1,1,1,0,0}은 내림차순 상태이므로
  • prev_permutation()을 써야 사전순으로 모든 조합을 생성 가능

7. 조합의 백트래킹(DFS) 구현

1
2
3
4
5
6
7
8
9
10
11
12
13
14
std::vector<int> comb;
void dfs(int start, int n, int r) {
    if (comb.size() == r) {
        for (int x : comb) std::cout << x << ' ';
        std::cout << '\n';
        return;
    }
    for (int i = start; i <= n; ++i) {
        comb.push_back(i);
        dfs(i + 1, n, r);
        comb.pop_back();
    }
}
// 시간복잡도: O(nCr)

8. 각 방식 비교 요약

목적추천 방식시간복잡도특징
조합 수 계산공식 / DPO(n) ~ O(n²)빠름
모든 조합 출력마스크 + prev_permutationO(nCr × n)STL 활용
조건부 탐색백트래킹(DFS)O(nCr)유연성 뛰어남

9. next_permutation vs prev_permutation 정리

항목next_permutationprev_permutation
동작 방향사전순 증가사전순 감소
시작 정렬오름차순 필요내림차순 필요
전체 순열 생성정렬 필수역정렬 필수

10. 중복 조합

공식

\[\binom{n + r - 1}{r}\]

마스크 구성 (별 *, 구분자 |)

  • 예: **|| → 첫 번째 항목 2개 선택
  • 총 칸 수: n + r - 1

예제 (A, B, C 중 2개 중복 선택)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
std::vector<std::string> base = {"A", "B", "C"};
std::vector<int> mask(4, 0);
std::fill(mask.begin(), mask.begin() + 2, 1);

do {
    int stars = 0;
    std::vector<std::string> result;
    for (int i = 0; i < 4; ++i) {
        if (mask[i]) stars++;
        else {
            result.insert(result.end(), stars, base[result.size()]);
            stars = 0;
        }
    }
    result.insert(result.end(), stars, base[result.size()]);
    for (auto& s : result) std::cout << s;
    std::cout << '\n';
} while (std::prev_permutation(mask.begin(), mask.end()));
// 시간복잡도: O(C(n+r-1, r) × n)

마무리 요약

항목설명
순열 생성next_permutation 사용 (정렬 필요)
조합 생성마스크 + prev_permutation 사용
중복 조합n + r - 1칸에서 마스크 구성
조건부 조합DFS(백트래킹) 방식 적합
성능 비교조합 출력은 O(nCr × n), 계산만 할 경우 O(n) 가능

void buildCombinationTable(int maxN) { for (int n = 0; n <= maxN; ++n) { comb[n][0] = comb[n][n] = 1; for (int r = 1; r < n; ++r) { comb[n][r] = comb[n - 1][r - 1] + comb[n - 1][r]; } } } // 시간복잡도: O(n^2)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
---

## 7. C++ STL로 순열 만들기 (`std::next_permutation`)

```cpp
#include <algorithm>
#include <vector>
#include <iostream>

int main() {
    std::vector<int> v = {1, 2, 3};
    do {
        for (int x : v)
            std::cout << x << ' ';
        std::cout << '\n';
    } while (std::next_permutation(v.begin(), v.end()));
}
// 시간복잡도: O(n! × n)

8. C++ STL로 조합 만들기 (std::prev_permutation + 마스크)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    int n = 5, r = 3;
    std::vector<int> data = {10, 20, 30, 40, 50};
    std::vector<int> mask(n, 0);
    std::fill(mask.begin(), mask.begin() + r, 1);

    do {
        for (int i = 0; i < n; ++i) {
            if (mask[i])
                std::cout << data[i] << ' ';
        }
        std::cout << '\n';
    } while (std::prev_permutation(mask.begin(), mask.end()));
}
// 시간복잡도: O(nCr × n)

9. 요약 정리

항목조합 (Combination)순열 (Permutation)
의미순서 없이 선택순서 있게 선택
공식nCr = n! / r!(n-r)!nPr = n! / (n-r)!
사용 예복권 번호 뽑기줄 세우기, 경로 정하기
STL 지원X (마스크 + perm 활용)next_permutation()
시간복잡도O(n) 또는 O(n^2)O(n!)
  • next_permutation은 순열을 사전순으로 생성합니다.
  • 조합을 사전순으로 출력하려면 입력과 마스크를 정렬한 뒤 prev_permutation() 사용이 효과적입니다.
  • 필요 시 백트래킹 또는 DFS 방식으로도 생성 가능.

10. 조합 생성 방식 비교 (백트래킹 vs STL)

방식특징시간복잡도장점단점
STL prev_permutation (마스크)마스크 조작으로 조합 생성O(nCr × n)구현 간단, STL 지원출력만 가능, 탐색 제어 어려움
백트래킹 (DFS)탐색 트리를 따라 직접 선택O(nCr)조건부 탐색 가능, 유연함구현 복잡, 재귀 깊이 큼
수학적 계산 (nCr 함수)조합 수만 구함O(n) 또는 O(n²)빠름, 값만 구할 때 적합직접 조합은 만들 수 없음

백트래킹 DFS 예제 (C++)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
#include <vector>
using namespace std;

vector<int> comb;
void dfs(int start, int n, int r) {
    if (comb.size() == r) {
        for (int x : comb) cout << x << ' ';
        cout << '\n';
        return;
    }
    for (int i = start; i <= n; ++i) {
        comb.push_back(i);
        dfs(i + 1, n, r);
        comb.pop_back();
    }
}

int main() {
    int n = 5, r = 3;
    dfs(1, n, r);
}

목적별 추천 방식

목적추천 방식
모든 조합을 빠르게 출력STL 마스크 + prev_permutation
조건부 조합 탐색백트래킹 (DFS)
조합 개수만 구함수학 공식(nCr) 또는 DP
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.