인턴을 하게 되면서 알고리즘 문제를 많이 접하게 되지만,, 나에게는 아직도 알고리즘은 머리 아픈 숙제와 가깝다.
그래도 인턴으로 있으면서 잊어버렸던 전공지식이 되살아나는 중이다. 함께 일하는 분들에게 어찌나 감사한지..
이번에는 집합 문제 풀 때 유용하게 사용할 수 있다는 비트 마스킹에 대해 알아보려고 한다.
그냥 재귀로 풀면 되는거 아냐?
생각했는데, 비트마스킹을 이용하면 훨씬 간단하고 빠른 속도로 알고리즘을 풀 수 있다는 사실을 배우게 되었다.
대학생 때 분명 배웠던 것 같은데..왜 이렇게 익숙해지지 않는지..
비트마스킹이란?
비트 마스킹은 정수의 이진수 표현을 자료구조로 사용해서 알고리즘을 효율적으로 구현하는 기법이다. 비트 연산자는 수학적 계산을 넘어서 다양한 문제를 간결하고 빠르게 해결할 수 있도록 도와준다. 특히 집합을 다룰 때 유용하게 사용되며, 정수 하나를 집합의 상태를 나타내는 비트로 사용한다. 예를 들어, 집합에 포함된 원소가 있으면 해당 비트를 1로, 없으면 0으로 설정하는 방식이다.
왜 비트 마스킹을 사용할까?
비트 마스킹을 사용하면 빠른 수행 시간, 간결한 코드, 적은 메모리 사용 등의 장점이 있다. 이는 비트 연산을 통해 데이터를 다룰 때의 특성 덕분이다. 집합 연산을 할 때마다 데이터를 하나씩 확인하는 대신, 비트 연산을 사용하면 여러 원소를 한 번에 처리할 수 있기 때문이다.
비트 연산자
a&b | AND 연산 둘다 1이면 1, 아니면 0 |
a|b | OR 연산 둘 다 0 이면 0, 아니면 1 |
a^b | a의 모든 비트와 b의 모든 비트를 XOR 연산. 둘이 다르다면 1, 아니며 0 |
~a | a의 모든 비트에 NOT 연산 0이면 1, 1이면 0 |
a<<b | a를 b비트만큼 왼쪽으로 시프트 ex) a=1=001(2), a<<2 = 100(2) =4 |
a>>b | a를 b비트만큼 오른쪽으로 시프트 ex) a=4=100(2), a>>2=001(2)=1 |
비트 마스킹 예시
가장 기본적인 예시로, 3개의 원소가 있는 집합을 다룬다고 가정하자. 이 집합을 비트 마스크를 이용해 표현할 것이다. 집합의 원소가 포함되었으면 해당 비트를 1로, 포함되지 않았으면 0으로 설정한다.
집합 예시
- 집합 A = {1, 3}
- 집합 B = {2, 3}
이 집합들을 비트 마스크로 표현할 때, 각각을 이진수로 변환할 수 있다.
- 집합 A는 {1, 3}이므로 이진수로 101 (왼쪽에서부터 차례대로 1, 2, 3번째 원소를 나타냄)
- 집합 B는 {2, 3}이므로 이진수로 011
1. 비트마스크로 집합 표현하기
// 집합 A와 집합 B
let A = 0b101; // {1, 3}
let B = 0b011; // {2, 3}
// 집합 A와 집합 B를 비트마스크로 출력
console.log(A.toString(2).padStart(3, '0')); // "101"
console.log(B.toString(2).padStart(3, '0')); // "011"
위 코드는 집합 A와 B를 각각 0b101과 0b011로 표현한다. 이진수로 출력할 때 padStart(3, '0')를 사용해서 3자리로 맞춘다.
2. 집합의 합집합 (OR 연산)
집합 A와 B의 합집합을 구할 때, 비트 OR 연산을 사용한다. 합집합은 두 집합에 존재하는 원소들을 모두 포함하는 집합이다.
let union = A | B; // A와 B의 합집합
console.log(union.toString(2).padStart(3, '0')); // "111"
합집합을 구하는 방법은 A | B로, OR 연산을 이용해서 두 집합의 원소를 모두 포함하는 새로운 집합을 만든다. 결과는 0b111로 {1, 2, 3}이 된다.
3. 집합의 교집합 (AND 연산)
교집합은 두 집합에 모두 존재하는 원소들만 포함하는 집합이다. 비트 AND 연산을 사용한다.
let intersection = A & B; // A와 B의 교집합
console.log(intersection.toString(2).padStart(3, '0')); // "001"
교집합을 구할 때는 A & B로, 두 집합에 공통으로 포함된 원소들만 남게 된다. 결과는 0b001로 {3}이 된다.
4. 집합의 차집합 (AND, NOT 연산)
차집합은 첫 번째 집합에는 포함되고 두 번째 집합에는 포함되지 않은 원소들만 남기는 집합이다. A & ~B로 구할 수 있다.
let difference = A & ~B; // A에서 B를 뺀 차집합
console.log(difference.toString(2).padStart(3, '0')); // "100"
차집합은 A & ~B로, B에 포함되지 않은 원소들만 남는다. 결과는 0b100으로 {1}이 된다.
5. 집합의 원소 포함 여부 체크
어떤 원소가 집합에 포함되어 있는지 확인하려면, 비트 AND 연산을 사용해 특정 비트가 1인지 확인한다. 예를 들어, 원소 2가 집합 A에 포함되어 있는지 확인하려면 A & (1 << 1)을 사용한다. 1 << 1은 2번째 비트를 나타낸다.
// 원소 2가 집합 A에 포함되어 있는지 확인
let element = 2;
let check = A & (1 << (element - 1)); // A에서 2번째 비트가 1인지 확인
console.log(check !== 0 ? "포함" : "미포함"); // "미포함"
(1 << (element - 1))는 2번째 비트를 1로 만든 값이다. 만약 집합 A에서 이 비트가 1이면 포함되어 있다고 판단한다.
이처럼 비트 마스킹을 사용하면 집합을 표현하고 집합 연산을 효율적으로 처리할 수 있다.
&, |, ^, ~ 등의 비트 연산자를 활용하면 간결하고 빠르게 집합 문제를 해결할 수 있다.
비트마스킹을 이용해 백준 문제를 풀어보면서 차차 적용해나가보자
(사실 요즘 백준은 무슨 후유증 때문에 실버문제도 읽기 힘든 지경이지만..)
https://www.acmicpc.net/problem/11723
https://www.acmicpc.net/problem/1094
'알고리즘' 카테고리의 다른 글
[프로그래머스] 완주하지 못한 선수 (0) | 2024.05.26 |
---|---|
[JavaScript] 시간 복잡도 줄이기 (0) | 2024.03.27 |
[JavaScript] 단순 서치 성능 개선하기 (0) | 2024.03.27 |
[JavaScript] map, reduce 함수 (0) | 2024.03.09 |