비트마스킹(Bit Masking) 이란?
1. 비트란 ?
- 데이터를 나타내는 최소 단위
- 모든 데이터는 0과 1의 조합으로 구성되고, 0 또는 1이 하나의 비트가 됨
- 1개의 비트는 2가지 상태를 나타낼 수 있다 -> n 개의 비트로는 2 ^ n 가지의 상태를 나타낼 수 있다.
2. 비트마스킹이란 ?
- 컴퓨터가 내부적으로 모든 자료를 이진수로 표현하는 특성을 이용해 정수의 이진수 표현을 자료구조로 쓰는 기법
- 비트 마스크를 이용하면, 더 빠른 수행 시간 + 더 간결한 코드 + 더 적은 메모리 사용 효과를 얻을 수 있다.
- 집합의 배열을 인덱스로 표현할 수 있다.
비트 연산자(논리 연산자)
연산자 | 표기 | 역할 | 예시 |
AND | a & b | a 와 b 두 비트 모두 1이면 1, 아니면 0 | a = 4 = 100(2) b = 7 = 111(2) a & b = 100(2) = 4 |
OR | a | b | a 와 b 두 비트 중 하나라도 1이면 0 | a = 2 = 010(2) b = 5 = 101(2) a | b = 111(2) = 7 |
XOR | a ^ b | a와 b 두 비트가 서로 다르다면 1, 같으면 0 | a = 3 = 011(2) b = 5 = 101(2) a ^ b = 110(2) = 6 |
NOT | ~ a | a의 모든 비트에 NOT 연산 0이면 1, 1이면 0 |
a = 3 = 011(2) ~ a = 100(2) = 4 |
Left-shift | a << b | a 를 b 비트 만큼 왼쪽으로 시프트 | a = 1 = 001(2) a << 2 = 100(2) = 4 |
Right-shift | a >> b | a 를 b 비트 만큼 오른쪽으로 시프트 | a = 4 = 100(2) a >> 2 = 011(2) = 1 |
집합의 표현
1. 집합의 표현
(1) 원소의 개수가 N 인 집합이 있을 때
- 각각의 원소를 0번부터 ~ (N-1)번까지 번호를 부여하고
- 번호에 해당하는 비트가 1이면 원소가 포함, 0이면 원소가 불포함 으로 하여 집합을 비트를 이용해 표현 가능
(2) 예시
- {A, B, C, D, E, F, G} 라는 집합의 원소 = 7개 -> 7개의 비트로 집합 표현 가능
- 각 원소마다, 비트를 하나씩 대응시켜 원소가 존재한다면 -> 1, 존재하지 않는다면 -> 0 으로 표현
- {A, B, C} 부분집합은 112 = 1110000(2) 로, {A} 부분집합은 64 = 1000000(2) 로 표현한다.
2. 원소 추가
(1) 원소 추가 과정
- 현재 상태 current 에 p 번 원소를 추가하는 상황에서,
- 현재 상태 current 에서 p 번 비트를 1로 바꿔줘야 한다.
- OR 연산 활용
- a | b 비트 연산자를 활용해 해결 가능
- 1 << p : p 번 비트만 1, 나머지 비트는 0으로 만들고
-> | 연산을 통해 current 의 p 번 비트는 1로 바뀌고, 나머지 비트는 원래 상태 유지
current = current | (1 << p)
(2) 예시
- 현재 이진수 current = 10101 일 때, i = 3 번째 비트 값을 1로 변경하고자 할 때,
- 10101 을 변경한 후에는 -> 11101 이 나와야 한다.
1) 1 << 3 적용 : 1000
2) 10101 | 01000 = 11101
3. 원소 삭제
(1) 원소 삭제 과정
- 현재 상태 current 에서 p 번 원소를 삭제하는 상황에서,
- p 번 비트를 0 으로 바꿔줘야 함
- AND 연산과 NOT 연산 활용
1) 1 << p : p번 비트만 1로, 나머지 비트는 0 으로 만들고
-> ~ 연산을 통해 p 번 비트만 0, 나머지 비트는 1로 만들고
-> & 연산을 통해 p 번 비트만 0 으로 바꾸고, 나머지는 현재 상태 current 와 동일하게 유지
2) ~1 << p : p 번 비트만 0 으로, 나머지 비트는 1로 만들고
-> & 연산을 통해 p 번 비트만 0 으로 바꾸고, 나머지는 현재 상태 current 와 동일하게 유지
current = current & (~1 << p)
current = current & ~(1 << p)
(2) 예시
- 현재 이진수 current = 11101 일 때, i = 3 번째 비트 값을 0으로 변경하고자 할 때,
- 11101 을 변경한 후에는 -> 10101 이 나와야 한다.
1) 1 << 3 적용 : 1000 -> 01000
2) ~ 1 << 3 적용 : 10111
3) 11101 & 10111 적용 : 10101
4. 원소 조회
(1) 원소 조회 과정
- 현재 이진수 current = 10101 일 때, p 번째 비트가 무슨 값인지 알고자 할 때,
- AND 연산 활용
- 1 << p : 1 번 비트만 1로, 나머지 비트는 0 으로 만들고
-> & 연산을 통해 p 번째 비트 값이 무엇인지 파악
current & (1 << p)
(2) 예시
- 아래와 같이, 결과값이 0 이 나오면 -> p 번째 비트 값이 0 임을 알 수 있다.
- 이때, 반대로 0이 아니면 무조건 1인 것이다.
current = 10101
3번째 비트 값 : 10101 & (1 << 3) = 10101 & 01000 = 0
4번째 비트 값 : 10101 & (1 << 4) = 10101 & 10000 = 10000
5. 원소 토글
(1) 원소 토글
- p 번 비트가 1 이면 -> 0 / 0이면 -> 1 로 바꾸는 연산
(2) 원소 토글 과정
- 현재 상태 current 에서 p 번 원소가 있다면 삭제하고, 없다면 추가하는 상황에서,
- 1 << p : p 번 비트만 1, 나머지 비트는 0 으로 만든다.
- current 의 나머지 비트들은, 0 과 XOR 연산 진행 : 0 이면 0, 1 이면 1 로 -> 현재 상태 유지
- p 번 비트는, 1과 XOR 연산 진행 : 1 이면 0, 0 이면 1로 토글됨
6. 집합 연산(합집합)
(1) a | b
- 두 집합 A, B 에 어느 하나에 존재하는 원소들의 비트는 모두 1로, 두 집합에 모두 존재하지 않는 원소들만 비트가 0 으로
(2) a & b
- 두 집합 A, B 모두 존재하는 원소들의 비트 : 1과 1에 대해 AND 연산 수행 - >1
- 나머지 0
(3) a & ~ b
- 집합 A와 집합 B의 여집합과 & 연산을 하므로, A - B 인 집합이 결과가 된다.
(4) a ^ b
- 집합 A 와 집합 B 둘 중 하나에만 포함된 원소들만 1 로 살아남게 됨
1. a 와 b 의 합집합
a | b
2. a 와 b 의 교집합
a & b
3. a 와 b 의 차집합(a에서 b를 뺀 차집합)
a & ~b
4. a 와 b 중 하나에만 포함된 원소들의 집합
a ^ b
7. 집합의 크기 구하기
- 집합의 크기를 구하려면, 현재 1로 켜져있는 비트 수를 Count 한다.
- 모든 비트를 순회해야 하므로 재귀적으로 구현 가능
1) x % 2 : 마지막 비트를 얻음
2) x / 2 : 마지막 비트 삭제
-> 모든 비트를 재귀적으로 순회
int bitCount(int x) {
if (x == 0) return 0;
return x % 2 + bitCount(x/2);
}
8. 모든 부분 집합 순회하기
- 어떤 집합의 모든 부분 집합을 순회하는 과정 구현 가능
for (int subSet = set; subSet; subSet = (subSet - 1) & set) {
// subSet은 set의 부분집합 중 하나
}
(1) 예시
- {A, B, D} 집합의 모든 부분 집합을 순회하고자 할 때,
- {A, B, D} 의 부분 집합은 공집합 제외 {A}, {B}, {D}, {A, B}, {A, D}, {B, D}, {A, B, D}
- 각 부분 집합을 비트로 표현하면 아래와 같다.
{A} | 1000 |
{B} | 0100 |
{D} | 0001 |
{A, B} | 1100 |
{A, D} | 1001 |
{B, D} | 0101 |
{A, B, D} | 1101 |
- set = 1101(2) = {A, B, D} 에 대해 위의 부분 집합 순회 과정을 처리하면
subSet | (subSet - 1) | (subSet - 1) & set |
1101(2) // {A, B, D} | 1100(2) | 1100(2) |
1100(2) // {A, B} | 1011(2) | 1001(2) |
1001(2) // {A, D} | 1000(2) | 1000(2) |
1000(2) // {A} | 0111(2) | 0101(2) |
0101(2) // {B, D} | 0100(2) | 0100(2) |
0100(2) // {B} | 0011(2) | 0001(2) |
0001(2) // {D} | 0000(2) | 0000(2) // 종료 |
- (subSet - 1) & set 을 통해 for 문을 돌면서 모든 부분 집합을 순회할 수 있다 !
참고자료
- 참고1
- 참고2
'Programming > Algorithm' 카테고리의 다른 글
[Algorithm] 프로그래머스 전화번호 목록(해시) (0) | 2022.05.13 |
---|---|
[Algorithm] 백트래킹(Backtracking) (0) | 2022.02.27 |
[Data Structure] 힙(Heap) 연산 (0) | 2022.02.21 |
[Data Structure] 트리(Tree) 자료구조 (0) | 2022.02.14 |
[Algorithm] DP(Dynamic Programming) (0) | 2022.01.15 |