바이너리 카운팅을 이용한 부분집합 구현과 재귀함수를 이용한 조합 구현하기
1. 부분집합 생성하기
비트 연산
1<<n은 비트 0000000....1을 왼쪽으로 n번 shift 했다는 소리
그래서 1<<n 자체로 $2^{n}$을 나타낸다
원소가 n개인 부분집합의 개수는 $2^{n}$개인데, 0부터 $2^{n}-1$까지를 2진수로 나타낸다면,
예를 들어 n=4이면 0부터 15까지가 0000, 0001, 0010, 0011, ..., 1111으로 나타난다
이 비트자체가 하나의 부분집합이라고 볼 수 있다.
무슨 말이냐면 원소의 개수가 n=4인 집합 {a,b,c,d}의 부분집합은..
(오른쪽에서) i번째 비트값이 1이면 {a,b,c,d}에서 해당하는 i번째 위치의 원소를 선택하는 것으로 구할 수 있다
예를 들어 0010은 1번째 비트값이 1이므로 {a,b,c,d}에서 1번째 원소인 b를 선택한 {b}를 나타내고
0110은 1번째, 2번째 비트값이 1이므로 {a,b,c,d}에서 {b,c}를 나타낸다고 생각할 수 있다
비트 연산의 &연산은 해당하는 비트가 1인지 아닌지 검사하는 특징이 있다
왜냐하면 1과 1의 and연산은 1이기 때문이다
그러므로 i & (1<<j)에 대해 생각해본다면, 1 << j는 00000000000000000.....1에서 맨 오른쪽에 있는 1을 j번 왼쪽으로 shift한 값이다.
이것과 이진수로 나타낸 어떤 i = 0000111110110101010101011100...1과 and연산을 수행한다면??
i의 j번째 bit가 1인지 아닌지를 검사한다
arr = [3,6,7]
n = len(arr) ##배열의 길이
for i in range(1<<n): ##i는 arr의 모든 부분집합
partial = [] ##i를 나타내는 부분집합
for j in range(n): ##i를 길이 n인 이진수로 나타낼때, 0~n-1의 비트 검사
if i & (1<<j): #i의 j번째 bit가 1이면...
partial.append(arr[j]) ##j번째 원소는 i가 나타내는 부분집합의 원소
print(partial)
[]
[3]
[6]
[3, 6]
[7]
[3, 7]
[6, 7]
[3, 6, 7]
2. 다중for문으로 단순히 조합 구현
5개 [1,2,3,4,5]중에서 3개를 고르는 조합은 어떻게 구할 수 있을까??
3개의 index i,j,k를 배열 내에서 움직이면서 찾을 수 있는데
중복된 원소를 고르지 않는다고 생각하면
첫번째 원소 i는 1,2,3까지 가능하다. j,k가 최소한 한자리는 차지해야하니까.. 최대 3,4,5쪽 까지 이동가능하거든
1,2,3중에서 i가 하나를 뽑았다면, j는 i+1부터 4까지 가능할 것이다.
i,j가 각각 뽑았다면, k는 i,j가 뽑지 않은 j+1부터 5까지 가능할 것이다.
#조합 단순 구현하기
N = 5
for i in range(1,N-1):
for j in range(i+1,N):
for k in range(j+1,N+1):
print(i,j,k)
3. 재귀함수를 이용한 조합 구현
많은 경우 임의의 n개에서 임의의 r개를 고르는 조합을 원한다
그럴 때마다 r이 얼마일지도 모르는데 r개의 for문을 다중으로 쓸 수는 없다
def nCr(n,r,s):
if r == 0:
print(*comb)
else:
for i in range(s,n-r+1):
comb[r-1] = A[i]
nCr(n,r-1,i+1)
A = [1,2,3,4,5]
n = len(A)
r = 3
comb = [0] * r
nCr(n,r,0)
3 2 1
4 2 1
5 2 1
4 3 1
5 3 1
5 4 1
4 3 2
5 3 2
5 4 2
5 4 3
5개 중에서 3개를 뽑는 조합.. comb = [0,0,0]에 3개의 수를 A = [1,2,3,4,5]에서 뽑아서 채워 넣는 방법
먼저 comb[2]에 for i in range(0,3):을 순회하여.. A[0] = 1이 들어가고..
r=2, i = 1인 상태로 nCr에 들어간다
다음 comb[1]에 for i in range(1,4):을 순회하여... A[1]=2이 들어간다..
r=1, i=2인 상태로 nCr에 들어간다
다음 comb[0]에 for i in range(2,5)을 순회하여 A[2] = 3이 들어간다..
r=0, i=3인 상태로 들어가는데... r=0이므로 comb = [3,2,1]이 완성된다
-----------------------------------------------------
그러면 다시 뒤로 돌아가서
""comb[0]에 for i in range(2,5)을 순회하여 A[2] = 3이 들어간다..""
이제 i = 3 시작.. comb[0]에 A[3] = 4가 들어간다
comb = [4,2,1]이 되고 r=0, i=4인 상태로 다음 재귀로 들어감. r=0이면 comb를 출력
그 후 다시 되돌아감
""comb[0]에 for i in range(2,5)을 순회하여 A[2] = 3이 들어간다..""
이제 i = 4 시작.. comb[0]에 A[4] = 5가 들어간다
comb = [5,2,1]이 되고 r=0, i=5인 상태로 들어가는데.. r=0이 되므로 comb를 출력
그 후 다시 되돌아가
""comb[0]에 for i in range(2,5)을 순회하여 A[2] = 3이 들어간다..""는 모든 for문을 순회해서 더 되돌아가
""다음 comb[1]에 for i in range(1,4):을 순회하여... A[1]=2이 들어간다..""
여기서 i=1이 끝났으니까 i=2 시작
comb[1]에 A[2] = 3이 들어가서.. comb[0,3,1]인 상태로 r=1, i=3인 상태가 다음 재귀로 들어감
다음 "comb[0]에 for i in range(3, 5):를 순회하면서... A[3]=4가 가 들어감"
그래서 comb = [4,3,1]이 완성되고 r=0, i=4인 상태로 다음 재귀로 들어가는데 r=0이므로 comb 출력..
.... 위 재귀를 계속 반복해서...
4. 재귀를 이용한 중복조합 구현
그렇다면.. 중복조합은 어떻게 구현할까?
위의 재귀 반복을 이해했다면 응용해서 가능하다
i에서 A[i]를 뽑고 다음 i+1로 이동한것은 i번은 다음에는 뽑지 않겠다는 것이다.
그렇다면 다음에도 i를 뽑을 수 있도록 만들면 된다
그리고 i의 범위가 문제인데
for i in range(s,n-r+1):은 i가 선택할 수 있는 범위를 나타내는 것이었다
r에 상관없이 i의 범위를 항상 s~n으로 해둔다면?
def nHr(n,r,s):
if r == 0:
print(*comb)
else:
for i in range(s,n):
comb[r-1] = A[i]
nHr(n,r-1,i)
A = [1,2,3,4,5]
n = len(A)
r = 3
comb = [0] * r
nHr(n,r,0)
1 1 1
2 1 1
3 1 1
4 1 1
5 1 1
2 2 1
3 2 1
4 2 1
5 2 1
3 3 1
4 3 1
5 3 1
4 4 1
5 4 1
5 5 1
2 2 2
3 2 2
4 2 2
5 2 2
3 3 2
4 3 2
5 3 2
4 4 2
5 4 2
5 5 2
3 3 3
4 3 3
5 3 3
4 4 3
5 4 3
5 5 3
4 4 4
5 4 4
5 5 4
5 5 5
그러면 처음에 comb[2]에 A[0]을 뽑고.. for i in range(0,5):를 순회할건데..
다음에 comb[1]에.. i=0인 상태로 들어가므로 A[0]을 뽑고 for i in range(0,5)를 순회할거고
다시 comb[0]에 i=0인 상태로 들어가고.. A[0]을 뽑고 for i in range(0,5)를 순회할거고..
comb = [1,1,1]이 완성되어 출력
뒤로 돌아가서 i=1인 상태에서 comb[0]에 A[1]을 뽑아서 [2,1,1]을 완성하여.. comb = [2,1,1]을 출력
다시 돌아가서.. [3,1,1], [4,1,1], [5,1,1]을 출력하고..
"다시 comb[0]에 i=0인 상태로 들어가고.. A[0]을 뽑고 for i in range(0,5)를 순회할거고.."여기가 끝..
더 되돌아가서..
""comb[1]에.. i=0인 상태로 들어가므로 A[0]을 뽑고 for i in range(0,5)를 순회할거고""에서 i=1이 시작되어
comb = [0,2,1]이고 i=1인 상태로 들어감
-----------------------------------------------------------
[1,2,1]도 가능한거 아니야?? 아니 조합은 [2,1,1]과 [1,2,1]은 같은거잖아
-----------------------------------------------------------
그러면 comb[0]에 A[1]을 뽑고 for i in range(1,5)를 순회...
이를 계속 반복하면.. 이것이 중복조합
'알고리즘 > 브루트포스' 카테고리의 다른 글
같은 것이 있는 순열(복수순열) 배우기 - 숫자 만들기 - (0) | 2022.10.12 |
---|---|
중복된, 비효율적인 연산을 줄이는 연습문제 -요리사- (0) | 2022.10.05 |
재귀함수를 이용한 순열 구현하기 (0) | 2022.09.26 |
2차원 배열에서 한 행, 한 열 당 숫자 하나씩을 골랐을 때 최소 합으로 만드는 방법 (0) | 2022.08.25 |
문자열 패턴 매칭 brute force 알고리즘 (0) | 2022.08.16 |