일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 다익스트라
- 소트 게임
- Backtracking
- 문자열
- 백준 17071
- C++ 17071
- 투포인터
- BFS
- C++
- 프로그래머스
- 인덱스 트리
- 백트래킹
- 가장 긴 증가하는 부분 수열
- 백준
- strtok
- 백준 숨바꼭질5
- 알고리즘
- DFS
- C++ 1918
- 순열
- 조합론
- 16933
- C++1167
- C++ 1937
- C++1967
- 조합
- DP
- LIS
- c언어
- 위상정렬
- Today
- Total
블로그
[C++] 순열(nPm), 조합(nCm), 중복순열(nPim), 중복조합(nHm) 본문
*모든 예시는 A, B, C, E, D의 다섯 원소 중 3개의 원소를 뽑는 것으로 다루었습니다
순열(nPm): 순서가 의미 있는 선택 -> "ABC"와 "ACB"를 다르게 취급하겠다는 것
"순서가 의미 있다" ==> 순서가 바뀌면 다른 것, 다른 경우가 된다
(A -> B -> C의 순서로 뽑음, A -> C -> B의 순서로 뽑음)
#include <iostream>
#include <vector>
using namespace std;
int n, m;
vector<int> nPm;
bool is_in[9];
void backtracking() {
if (nPm.size() == m) {
for (int i : nPm) printf("%d ", i);
printf("\n");
return;
}
for (int i = 1; i <= n; i++) {
if (is_in[i]) continue;
is_in[i] = true;
nPm.push_back(i);
backtracking();
nPm.pop_back();
is_in[i] = false;
}
}
int main() {
scanf("%d%d", &n, &m);
backtracking();
}
조합(nCm): 순서가 의미 없는 선택 -> "ABC"와 "ACB"는 동일하게 취급하겠다는 것.
"ABC", "ACB", "BAC", "BCA", "CAB", "CBA" 모두 같은 것으로 취급함 (A, B, C를 뽑은 경우 1)
#include <iostream>
#include <vector>
using namespace std;
int n, m;
vector<int> nCm;
void backtracking(int here) {
if (nCm.size() == m) {
for (int i : nCm) printf("%d ", i);
printf("\n");
return;
}
for (int i = here; i <= n; i++) {
nCm.push_back(i);
backtracking(i + 1);
nCm.pop_back();
}
}
int main() {
scanf("%d%d", &n, &m);
backtracking(1);
}
중복순열(nPim): 중복을 허용하며 순서가 의미 있는 선택 -> "AAB", "ABA"는 다른 것으로 취급
#include <iostream>
#include <vector>
using namespace std;
int n, m;
vector<int> nPim;
void backtracking() {
if (nPim.size() == m) {
for (int i : nPim) printf("%d ", i);
printf("\n");
return;
}
for (int i = 1; i <= n; i++) {
nPim.push_back(i);
backtracking();
nPim.pop_back();
}
}
int main() {
scanf("%d%d", &n, &m);
backtracking();
}
중복 조합(nHm): 중복을 허용하며 순서는 의미 없는 선택 -> "AAB", "ABA"는 같은 것
"AAB", "ABA", "BAA" 모두 A를 두 개, B를 하나 선택한 경우 1에 속함
#include <iostream>
#include <vector>
using namespace std;
int n, m;
vector<int> nHm;
void backtracking(int here) {
if (nHm.size() == m) {
for (int i : nHm) printf("%d ", i);
printf("\n");
return;
}
for (int i = here; i <= n; i++) {
nHm.push_back(i);
backtracking(i);
nHm.pop_back();
}
}
int main() {
scanf("%d%d", &n, &m);
backtracking(1);
}
위의 모든 예시들은 N 개 중에서 M개를 선택하는 것이 가정되어 있지만
위의 코드들을 응용하여 N 개 중에서 (1 ~ M) 개를 선택하는 모든 경우의 수를 구하는 것도 가능하다

예를 들어, 프로그래머스의 "모음사전" 문제의 경우
https://school.programmers.co.kr/learn/courses/30/lessons/84512
프로그래머스
코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.
programmers.co.kr
A, E, I, O, U의 문자들을 중복해서 순서가 의미 있게 선택하므로 중복순열이지만
5개에서 특정 M개를 고르는 것이 아닌 1~M개를 고르는 경우의 수 모드를 살펴 보아야 한다
위의 문제를 중복순열 코드를 이용해서 푼 코드를 첨부한다
물론 위 문제는 훨씬 간단한 풀이법이 있다...^^*
해당 코드는 5(=N)개의 모음 중 1~M개를 고르는 경우의 수를 모두 살펴보아야 하므로
5Pi1 + 5Pi2 + 5Pi3 + 5Pi4 + 5Pi5 = 5^1 + 5^2 + 5^3 + 5^4 + 5^5 = 3,905 의 경우의 수를 계산한다
(가지치기를 하긴 하지만.. 찾고자 하는 단어가 사전 상 맨 마지막이면 의미없는 가지치기이다)
원래 nPim 의 계산이 n^m이지만 이 경우는
이므로
칸의 개수나 모음의 개수가 커지면 시간복잡도가 어마어마해질 것이다..
#include <string>
#include <vector>
using namespace std;
char moum[5] = {'A', 'E', 'I', 'O', 'U'};
int idx = 0;
int ans;
bool found = false;
void nPim(string str, string word) {
if(found) return;
if(str.length() > 5) return;
if(str.length() >= 1) idx++;
if(str == word) {
ans = idx;
found = true;
return;
}
for(int i = 0; i<5; i++) {
string tmp(str);
tmp += moum[i];
nPim(tmp, word);
}
}
int solution(string word) {
int answer = 0;
nPim("", word);
answer = ans;
return answer;
}
보면 재귀함수의 탈출조건을 ==5 에서 >5로 바꿨다
그리고 >=1 이상일 때 찾고자 하는 조건이 만족되었는지 확인해준다
(원래는 ==m 일 때만 해주었지만 여기서는 ==1~m일때를 다 봐야 하므로)
조합(nCm)을 예시로 코드 이해하기
기본 순열과 조합의 코드 비교하며 이해하기
#include <iostream>
#include <vector>
using namespace std;
int N = 3;
int M = 2;
vector<int> nCm;
void backtrackingC(int start) {
if (nCm.size() == M) {
for (int i = 0; i < M; i++) {
printf("%d ", nCm[i]);
}
printf("\n");
return;
}
//여기를 start부터 시작하는거는 오름차순이라는 뜻임
//-> 순열이 아닌 조합일 수밖에 없음
//순열이라면 135 도 정답이고 531도 정답이므로
for (int i = start; i <= N; i++) {
nCm.push_back(i);
backtrackingC(i + 1);
nCm.pop_back();
}
}
bool visit[6];
vector<int> nPm;
void backtrackingP() {
if (nPm.size() == M) {
for (int i = 0; i < M; i++) {
printf("%d ", nPm[i]);
}
printf("\n");
return;
}
for (int i = 1; i <= N; i++) {
if (visit[i]) continue;
visit[i] = true;
nPm.push_back(i);
backtrackingP();
nPm.pop_back();
visit[i] = false;
}
}
int main() {
backtrackingC(1);
backtrackingP();
}
'알고리즘 정리' 카테고리의 다른 글
Union - Find 알고리즘 pseudo 코드 + 알고리즘 동작 방식 (0) | 2022.09.01 |
---|---|
MST 구하는 알고리즘 - 크루스칼, 프림 정리 (0) | 2022.09.01 |
최단경로(최단거리) 알고리즘 정리 - 다익스트라, 벨만포드, 플로이드 워셜 (0) | 2022.09.01 |
이분탐색 (0) | 2022.01.15 |
DFS 원형 -> 2022.9.23 확인 (0) | 2021.01.29 |