집합과 맵이 푸는 문제
배열에서 "이 값이 들어 있나?"를 확인하려면 처음부터 끝까지 훑어 \(O(N)\)이 듭니다.
집합(set) 과 맵(map) 은 이 검색을 훨씬 빠르게 만들어 주는 자료구조입니다.
- 집합: 값들의 모임. 중복을 허용하지 않고, 있는지/없는지를 빠르게 확인.
- 맵: 키 → 값의 대응. "이 단어가 몇 번 나왔나"처럼 무언가를 세거나 매핑.
1. 두 가지 구현: 해시 vs 트리
| 구분 | 해시 기반 | 트리 기반 |
|---|---|---|
| C++ | unordered_set/map |
set/map |
| Python | set, dict |
(별도, 정렬 필요시 직접) |
| 평균 검색 | \(O(1)\) | \(O(\log N)\) |
| 순서 | 없음 | 키가 정렬됨 |
해시 기반은 평균적으로 더 빠르지만 순서가 없습니다. 트리 기반은 약간 느린
대신 항상 정렬된 순서를 유지하고, "이 값 이상인 가장 작은 키" 같은 질의를
지원합니다. 무엇이 필요한지에 따라 고릅니다.
2. 집합으로 중복 제거하기
set<int> s;
for (int i = 0; i < n; i++) { int x; cin >> x; s.insert(x); }
cout << s.size() << '\n'; // 서로 다른 값의 개수
nums = set()
for _ in range(n):
nums.add(int(input()))
print(len(nums)) # 서로 다른 값의 개수
insert/add는 이미 있는 값을 다시 넣어도 무시되므로, 자연스럽게 중복이
제거됩니다.
3. 맵으로 개수 세기
가장 흔한 활용입니다. "각 단어가 몇 번 등장했는가?"
map<string, int> cnt;
cnt[word]++; // 없으면 0에서 시작해 1 증가
from collections import Counter
cnt = Counter(words) # 한 줄로 빈도 집계
print(cnt["apple"])
C++에서 cnt[word]++는 키가 없으면 자동으로 0을 만든 뒤 증가시킵니다.
이 동작 때문에 "단순히 조회만 했는데 키가 생겨 버리는" 부작용을 주의해야 합니다.
4. 복잡도 감각
\(N\)개의 값을 모두 넣고 모두 조회한다면
- 해시 기반: 평균 \(O(N)\)
- 트리 기반: \(O(N \log N)\)
둘 다 배열을 매번 훑는 \(O(N^2)\)보다 압도적으로 빠릅니다. "검색·중복 제거·빈도
집계가 반복되면 집합/맵"이라는 반사신경을 기르세요.
5. 해시의 최악 경우
unordered_map은 평균 \(O(1)\)이지만, 악의적으로 설계된 입력에서는 해시 충돌로
\(O(N)\)까지 느려질 수 있습니다(해시 충돌 공격). 안정성이 중요하면 트리 기반
map을 쓰거나 커스텀 해시를 적용합니다. 파이썬 dict도 정수/문자열 키에서
실용적으로 매우 빠르지만 같은 원리를 따릅니다.
정리
"있나 없나"는 집합, "몇 개·무엇과 짝"은 맵. 정렬된 순서가 필요하면 트리 기반,
순수 속도만 필요하면 해시 기반을 고르세요. 다음 강의에서 실제 API를 다룹니다.