전공 공부/Algorithm

[C++] 순열(Permutation) 조합(Combination) 알고리즘

흠냐아뤼 2020. 1. 28. 17:10
728x90

 

백준에서 완전 탐색 문제를 풀다가

항상 조합과 순열을 만들 때 헷갈려서

아예 시간을 내어 정리하였다.

 

이 네 가지 알고리즘의 뼈대를 이해하면, 여러 방면에 쓰여서 좋은 거 같다.

 

이후 나오는 모든 코드의 n과 r은 다음과 같다. (n개 중에서 r개 선택)

#define n 4
#define r 3

 

1) 순열

순서를 따지고, 중복을 허용하지 않는다. (순서O, 중복X)

중복을 검사하기 위한 check 배열을 사용한다.

depth 를 하나씩 늘려가며, 배열에 하나씩 채우는 방법으로 재귀를 수행한다.

int pArr[r] = { 0, };
bool check[n + 1] = { false, }; 

void permutation(int depth){
    if(depth == r){
        printArray(pArr);
        return;
    }
    
    for(int i = 1; i <= n; i++){
        if(!check[i]){
            check[i] = true;
            pArr[depth] = i;
            permutation(depth + 1);
            check[i] = false;
        }
    }
}

int main(void){
    cout << "순열 (순서o, 중복x)" << endl;
    permutation(0);
    
    return 0;
}
(base) yunhongchan-ui-MacBook-Pro:week5 hongchanyun$ g++ permutation.cpp -o main.out
(base) yunhongchan-ui-MacBook-Pro:week5 hongchanyun$ ./main.out
순열 (순서o, 중복x)
1 2 3 
1 2 4 
1 3 2 
1 3 4 
1 4 2 
1 4 3 
2 1 3 
2 1 4 
2 3 1 
2 3 4 
2 4 1 
2 4 3 
3 1 2 
3 1 4 
3 2 1 
3 2 4 
3 4 1 
3 4 2 
4 1 2 
4 1 3 
4 2 1 
4 2 3 
4 3 1 
4 3 2 

 

2) 중복 순열

순서를 따지고, 중복을 허용한다. (순서O, 중복O)

중복 검사를 제외하면 위의 순열 코드와 동일하다.

int dpArr[r] = { 0, };

void duplicatePermutation(int depth){
    if(depth == r){
        printArray(dpArr);
        return;
    }

    for(int i = 1; i <= n; i++){
        dpArr[depth] = i;
        duplicatePermutation(depth + 1);
    }
}

int main(void){
    cout << "중복 순열 (순서o, 중복o)" << endl;
    duplicatePermutation(0);

    return 0;
}
(base) yunhongchan-ui-MacBook-Pro:week5 hongchanyun$ g++ permutation.cpp -o main.out
(base) yunhongchan-ui-MacBook-Pro:week5 hongchanyun$ ./main.out
중복 순열 (순서o, 중복o)
1 1 1 
1 1 2 
1 1 3 
1 1 4 
1 2 1 
1 2 2 
1 2 3 
1 2 4 
1 3 1 
1 3 2 
1 3 3 
1 3 4 
1 4 1 
1 4 2 
1 4 3 
1 4 4 
2 1 1 
2 1 2 
2 1 3 
2 1 4 
2 2 1 
2 2 2 
2 2 3 
2 2 4 
2 3 1 
2 3 2 
2 3 3 
2 3 4 
2 4 1 
2 4 2 
2 4 3 
2 4 4 
3 1 1 
3 1 2 
3 1 3 
3 1 4 
3 2 1 
3 2 2 
3 2 3 
3 2 4 
3 3 1 
3 3 2 
3 3 3 
3 3 4 
3 4 1 
3 4 2 
3 4 3 
3 4 4 
4 1 1 
4 1 2 
4 1 3 
4 1 4 
4 2 1 
4 2 2 
4 2 3 
4 2 4 
4 3 1 
4 3 2 
4 3 3 
4 3 4 
4 4 1 
4 4 2 
4 4 3 
4 4 4 

 

728x90

 

3) 조합

순서를 따지지 않고, 중복을 허용하지 않는다. (순서X, 중복X)

반복문을 돌며, 모든 경우에 대해 선택하는 것은 위의 경우들과 같다.

그러나, 반복문의 시작 값은 이전에 선택한 값 + 1이 된다.

int cArr[r] = { 0, };

void combination(int depth, int next){
    if(depth == r){
        printArray(cArr);
        return;
    }

    for(int i = next; i <= n; i++){
        cArr[depth] = i;
        combination(depth + 1, i + 1);
    }
}

int main(void){
    cout << "조합 (순서x, 중복x)" << endl;
    combination(0, 1);

    return 0;
}
(base) yunhongchan-ui-MacBook-Pro:week5 hongchanyun$ g++ combination.cpp -o main.out
(base) yunhongchan-ui-MacBook-Pro:week5 hongchanyun$ ./main.out
조합 (순서x, 중복x)
1 2 3 
1 2 4 
1 3 4 
2 3 4

 

4) 중복 조합

순서를 따지지 않고, 중복을 허용한다. (순서X, 중복O)

조합과 구현이 비슷하나,

반복문의 시작 값은 이전에 선택한 값이 된다.

int dcArr[r] = { 0, };

void duplicateCombination(int depth, int next){
    if(depth == r){
        printArray(dcArr);
        return;
    }

    for(int i = next; i <= n; i++){
        dcArr[depth] = i;
        duplicateCombination(depth + 1, i);
    }
}

int main(void){
    cout << "조합 (순서x, 중복o)" << endl;
    duplicateCombination(0, 1);

    return 0;
}
(base) yunhongchan-ui-MacBook-Pro:week5 hongchanyun$ g++ permutation.cpp -o main.out
(base) yunhongchan-ui-MacBook-Pro:week5 hongchanyun$ ./main.out
조합 (순서x, 중복o)
1 1 1 
1 1 2 
1 1 3 
1 1 4 
1 2 2 
1 2 3 
1 2 4 
1 3 3 
1 3 4 
1 4 4 
2 2 2 
2 2 3 
2 2 4 
2 3 3 
2 3 4 
2 4 4 
3 3 3 
3 3 4 
3 4 4 
4 4 4

 

 

 

익숙해졌다면, 아래의 문제들을 풀어보며 완전히 자기 것으로 만들어보자.

 

https://www.acmicpc.net/workbook/view/2052

 

문제집: N과 M (baekjoon)

 

www.acmicpc.net

 

728x90