순열의 모든것 풀어봅시다
본문 바로가기
알고리즘 모음집/알고리즘 (Algorithm)

순열의 모든것 풀어봅시다

by KyeongMin 2019. 8. 20.
728x90
반응형

순열은 무엇일까요?  재귀를 이용하여 순열을 뽑기 전에 순열은 어떤 모양을 가지고 있는지 

알아야 함으로 어떻게 생겼는지 먼저 알아봅시다.

 순열은  Permutation 으로 nPr n개의 나열된 숫자에서 r개를 뽑는 것을 말합니다.

예를 들어 1, 2, 3, 4 의 숫자에서 2개의 숫자를 뽑는다면 경우는 

위와 같이 같은 숫자를 가진 집합이 존재하면서 뽑히게 됩니다. 1 2와 2 1이 같은 경우로 치지 않고 뽑히는 경우 

달라진다면 순열로 뽑는것이 옳고 1 2와 2 1이 같은 것이라면 조합으로 뽑아야 시간을 줄일 수 있겠죠?

조합은 위에 말한것처럼 순열의 반의 가짓수를 뽑는다고 생각하시면 좀 더 이해하기 쉬울 것 같습니다.

ㅇ 조합에 대한 이야기는 다음 블로그 글을 참조하시면 됩니다.

 

 

 

https://www.acmicpc.net/problem/15649

 

15649번: N과 M (1)

한 줄에 하나씩 문제의 조건을 만족하는 수열을 출력한다. 중복되는 수열을 여러 번 출력하면 안되며, 각 수열은 공백으로 구분해서 출력해야 한다. 수열은 사전 순으로 증가하는 순서로 출력해야 한다.

www.acmicpc.net

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include<stdio.h>
#include<vector>
using namespace std;
int N, M;
const int DSIZE = 9;
const int chkSIZE = 9;
int D[DSIZE];
int chk[chkSIZE];
void dfs(int idx) {
    if (idx == M) {
        for (int i = 0; i < M; i++printf("%d ", D[i]);
        printf("\n");
        return;
    }
    for (int i = 1; i <= N; i++) {
        if (chk[i] == 0) {
            chk[i] = 1;
            D[idx] = i;
            dfs(idx + 1);
            D[idx] = 0;
            chk[i] = 0;
        }
    }
}
void init() {
    scanf("%d %d"&N, &M);
}
int main(void) {
    init();
    dfs(0);
    return 0;
}
 
 
위의 소스를 보시면 숫자의 1 2 3 4 인경우 2개를뽑을때 1 1 이렇게 같은 숫자가
뽑히지 않기위해서 chk 배열을 이용하여 중복이 생기지 않게 했습니다.
이때 나는 중복을 있게 1 1  /  1 2  /  1 3  /  1 4  /  2 1  /  2 2 ...
이렇게 뽑고 싶은경우 chk 가 들어간경우를 빼주면 됩니다.
가장 기본적인 틀이니 외워놓으면 그냥 모든 경우를 적용할수 있습니다.

https://www.acmicpc.net/problem/15654

 

15654번: N과 M (5)

N개의 자연수와 자연수 M이 주어졌을 때, 아래 조건을 만족하는 길이가 M인 수열을 모두 구하는 프로그램을 작성하시오. N개의 자연수는 모두 다른 수이다. N개의 자연수 중에서 M개를 고른 수열

www.acmicpc.net

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include<stdio.h>
#include<algorithm>
using namespace std;
int N, M;
const int inputSIZE = 8;
const int chkSIZE = 8;
const int DSIZE = 8;
int input[inputSIZE];
int D[DSIZE];
int chk[chkSIZE];
void init() {
    scanf("%d %d"&N, &M);
    for (int i = 0; i < N; i++) {
        scanf("%d"&input[i]);
    }
    sort(input, input + N);
}
void dfs(int idx) {
    if (idx == M) {
        for (int i = 0; i < M; i++)printf("%d ", D[i]);
        printf("\n");
    }
    for (int i = 0; i < N; i++) {
        if (chk[i] == 0) {
            chk[i] = 1;
            D[idx] = input[i];
            dfs(idx + 1);
            D[idx] = 0;
            chk[i] = 0;
        }
    }
}
int main(void) {
    init();
    dfs(0);
    return 0;
}
 
 

위에 처음 문제와 다른점은 내가 입력한 숫자에 대해서 오름차순으로 뽑는 것입니다.

기본 틀에서 입력으로 받은 배열을 정렬을 해주고 

i의 값을 D의 배열에 넣어서 한경우는 1-N  N이 8이 최대인 경우에서 는 i값을 넣어도 되지만

입력받은 숫자를 넣어야 하므로 input [i]를 넣어주어야 정확한 결과를 얻을 수 있습니다.

https://www.acmicpc.net/problem/15663

 

15663번: N과 M (9)

한 줄에 하나씩 문제의 조건을 만족하는 수열을 출력한다. 중복되는 수열을 여러 번 출력하면 안되며, 각 수열은 공백으로 구분해서 출력해야 한다. 수열은 사전 순으로 증가하는 순서로 출력해야 한다.

www.acmicpc.net

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include<stdio.h>
#include<vector>
#include<queue>
#include<algorithm>
#include<map>
using namespace std;
map <vector<int>int> m;
int N, M, ret;
const int doubleChkSIZE = 10001;
const int DSIZE = 8;
const int chkSIZE = 8;
int doubleChk[doubleChkSIZE];
vector<int>D;
int chk[chkSIZE];
vector<int>input;
void init() {
    scanf("%d %d"&N, &M);
    for (int i = 0; i < N; i++) {
        int data = 0;
        scanf("%d"&data);
            input.push_back(data);
    }
    sort(input.begin(), input.end());
}
void dfs(int idx) {
    if (idx == M) {
        for (int i = 0; i < M; i++) {
            printf("%d ", D[i]);
        }
        printf("\n");
        return;
    }
    int before = 0;
    for (int i = 0; i < input.size(); i++) {
        if (chk[i] == 0&&before!=input[i]){ 
            before = input[i];
            chk[i] = 1;
            D.push_back(input[i]);
            dfs(idx + 1);
            D.pop_back();
            chk[i] = 0;
        }
    }
}
int main(void) {
    int T;
    //scanf("%d", &T);
    for (int tc = 1; tc <= 1; tc++) {
        init();
        dfs(0);
 
        //printf("#%d %d\n", tc, ret);
    }
    return 0;
}
 
 
cs

앞서 봤던 문제에 비해서 좀 더 생각을 해야 하는 문제입니다.

입력을 받은 수에 예를 들어 9 7 9 1 이면 정렬 시 1 7 9 9 가 되는데

이때 그냥 뽑게 되면 1 7 / 1 9 / 1 9 / 이렇게 정말 완전 같은 수를 가지게 뽑게 됩니다.

이수를 뽑지 않게 하기 위해서는 D 공간에 넣기전에 우선 중복이 되면 안되니까 중복 체크하는

chk 배열을 체크하고 before라는 변수를 두어 현재 정렬되어있으므로 이전의 값과 현재 지금

들어가서 D공간에 입력되는 값이 다른 경우만 넣어주게 되면 위의 문제는 해결될 수 있습니다.

이해가 안 되면 우선 코드를 입력해보시고 어떻게 결과가 나오게 되는지 확인해보시면 이해하기

수월할 것입니다.

중복순열

 

중복 순열은 이전에 봤던 순열과 비슷합니다. 아래의 문제를 보면서 이해해 봅시다.
https://www.acmicpc.net/problem/15651

 

 

15651번: N과 M (3)

한 줄에 하나씩 문제의 조건을 만족하는 수열을 출력한다. 중복되는 수열을 여러 번 출력하면 안되며, 각 수열은 공백으로 구분해서 출력해야 한다. 수열은 사전 순으로 증가하는 순서로 출력해야 한다.

www.acmicpc.net

1.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include<stdio.h>
#include<vector>
#include<algorithm>
using namespace std;
const int DSIZE = 8;
const int chkSIZE = 9;
int D[DSIZE];
int chk[chkSIZE];
int N, M;
void init() {
    scanf("%d %d"&N, &M);
}
void dfs(int idx) {
    if (idx == M) {
        for (int i = 0; i < M; i++)printf("%d ", D[i]);
        printf("\n");
    }
    for (int i=1; i <= N; i++) {
        if (idx < M) {
            D[idx] = i;
            dfs(idx + 1);
            D[idx] = 0;
        }
        
    }
}
int main(void) {
    init();
    dfs(0);
    return 0;
}
http://colorscripter.com/info#e" target="_blank" style="color:#e5e5e5text-decoration:none">Colored by Color Scripter
s
 
이문제는 위와 같이 해도되는데 아까 제일 처음에 있던 chk만 빼면 
1 1/ 1 2/ 1 3/ 1 4/ 2 1/ 2 2/... 이렇게 되는것가 비슷함으로 그렇게 해도되고 
위와같이 해도되면 위에서 if조건을 빼면 chk랑 뺀것과 같은 결과이니 여러방식을 
해보고 자신에게 맞는 방식으로 외우시면 됩니다.
 
2.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include<stdio.h>
#include<vector>
#include<algorithm>
using namespace std;
const int DSIZE = 8;
const int chkSIZE = 9;
char D[16];
int chk[chkSIZE];
int N, M;
void init() {
    scanf("%d %d"&N, &M);
}
void dfs(int idx) {
    if (idx >= (M-1)*2) {
        printf("%s\n", D);
        return;
    }
    D[idx++= ' ';
 
 
    for (int i=1; i <= N; i++) {
        D[idx] = '0' + i;
        dfs(idx + 1);
        
    }
}
int main(void) {
    init();
    for (int i = 1; i <= N; i++) {
        D[0= '0' + i;
        dfs(1);
    }
    return 0;
}
cs

위의 소스는 좀 더 빠른 방식으로 할 수 있는 방법으로 배열에 저장을 할 때 띄움도

같이 넣어야 하므로 M*2인데 dfs에서 (M-1)* 2 인 이유는 dfs를 들어오기 전에

제일 처음 값은 넣고 시작하기 때문에 M-1입니다.

그렇게 해서 이해하시면 빠르시고 그냥 나는 저런 방법 너무 복잡해서 어렵다 하면 1번 방식을 외우시면 됩니다.

https://www.acmicpc.net/problem/15656

 

 

15656번: N과 M (7)

N개의 자연수와 자연수 M이 주어졌을 때, 아래 조건을 만족하는 길이가 M인 수열을 모두 구하는 프로그램을 작성하시오. N개의 자연수는 모두 다른 수이다. N개의 자연수 중에서 M개를 고른 수열 같은 수를 여러 번 골라도 된다.

www.acmicpc.net

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include<stdio.h>
#include<vector>
#include<algorithm>
using namespace std;
int N, M;
const int DSIZE = 8;
const int inputSIZE = 8;
int D[DSIZE];
int input[inputSIZE];
void init() {
    scanf("%d %d"&N, &M);
    for (int i = 0; i < N; i++) {
        scanf("%d"&input[i]);
    }
    sort(input, input + N);
}
void dfs(int idx) {
    if (idx == M) {
        for (int i = 0; i < M; i++printf("%d ", D[i]);
        printf("\n");
        return;
    }
    for (int i = 0; i < N; i++) {
        if (idx < M) {
            D[idx] = input[i];
            dfs(idx + 1);
            D[idx] = 0;
        }
    }
}
int main(void)
{
    init();
    dfs(0);
    return 0;
}
 
 

위의 문제와 별 다를 바 없습니다 i를 D에 넣어줬던 것을 input [i]로만 넣어주면 되는 것입니다.

정말 문제들이 비슷비슷해서 쉽지 않나요? 

처음이라 어렵다고 생각이 들어도 여러 번 반복하면서 익히다 보면 언젠가는 

나 자신의 것이 될 것입니다. 포기하지 마세요.

여기까지 오시느라 수고 많으셨고 정말 알고 보면 쉽지 않습니까? 당연한 소리 같지만

반복을 통해 익숙해서 나만의 것으로 만들어보면 비슷한 유형의 문제를 풀어도 

겁이 안 날것입니다. 

위에 소스가 완벽한 소스가 아니고 제가 의식의 흐름에 따라 작성한 소스라 빼도 되는

부분이 있을 것이고 할 것인데 참고 정도로만 생각해주시고 위의 소스를 기반으로 더 완벽한

소스를 만들어보시면 좋을 것 같습니다. 

대게 소스를 배열 기반으로 했지만 백터 기반으로도 가능하니 백터를 이용한 방식으로도 해보셔도

좋다고 생각합니다.

오늘은 여기까지이고 감사합니다.

 

728x90
반응형

댓글