Ch.3 해시맵과 해시셋
해시 충돌과 해결 — Chaining vs Open Addressing
두 사람의 이름이 같은 사물함 번호를 받았다면?
해시 함수는 완벽하지 않습니다. 서로 다른 키가 같은 인덱스를 가리킬 수 있죠. 이 '충돌'을 어떻게 해결할까요?
충돌이 많아지면 O(1)이 깨지지 않나?
Chaining(연결 리스트)과 Open Addressing(빈자리 찾기), 두 전략으로 충돌을 관리합니다.
핵심 개념
해시 충돌
서로 다른 키가 같은 해시 인덱스를 가리키는 현상
Load Factor
저장된 데이터 수 / 배열 크기. 충돌 확률의 지표
핵심 내용
비둘기집 원리를 아시나요? 11마리 비둘기, 10개 집 → 최소 1개 집에 2마리!
# 충돌 예시
def simple_hash(key, size):
return sum(ord(c) for c in key) % size
# 배열 크기 = 10
print(simple_hash("abc", 10)) # (97+98+99) % 10 = 4
print(simple_hash("bac", 10)) # (98+97+99) % 10 = 4 ← 충돌!"abc"와 "bac"는 다른 문자열이지만 같은 인덱스 4를 받았습니다!
해결법 ①: 같은 인덱스에 연결 리스트를 매달아 여러 값을 저장!
class ChainingHashTable:
def __init__(self, size=10):
self.size = size
self.table = [[] for _ in range(size)] # 각 슬롯 = 리스트
def _hash(self, key):
return hash(key) % self.size
def put(self, key, value):
idx = self._hash(key)
# 이미 같은 키가 있으면 덮어쓰기
for i, (k, v) in enumerate(self.table[idx]):
if k == key:
self.table[idx][i] = (key, value)
return
self.table[idx].append((key, value))
def get(self, key):
idx = self._hash(key)
for k, v in self.table[idx]:
if k == key:
return v
return NoneChaining 장점: 구현이 간단, 삭제가 쉬움 Chaining 단점: 리스트가 길어지면 O(n)으로 퇴화
해결법 ②: 충돌하면 빈 슬롯을 찾아 이동합니다
class LinearProbingHashTable:
def __init__(self, size=10):
self.size = size
self.keys = [None] * size
self.values = [None] * size
def _hash(self, key):
return hash(key) % self.size
def put(self, key, value):
idx = self._hash(key)
while self.keys[idx] is not None:
if self.keys[idx] == key: # 같은 키면 덮어쓰기
self.values[idx] = value
return
idx = (idx + 1) % self.size # 다음 슬롯으로!
self.keys[idx] = key
self.values[idx] = value
def get(self, key):
idx = self._hash(key)
while self.keys[idx] is not None:
if self.keys[idx] == key:
return self.values[idx]
idx = (idx + 1) % self.size
return None두 전략의 장단점을 한눈에 비교해봅시다
Load Factor = n / m 데이터가 많아지면 충돌이 급증합니다
Load Factor = 저장된 항목 수(n) / 배열 크기(m) • LF < 0.7 → 성능 양호 • LF > 0.7 → 충돌 급증 → 리사이즈 필요! • Python dict는 LF > 2/3일 때 자동 리사이즈
리사이즈 = 배열을 2배로 키우고 모든 키를 다시 해싱합니다 (O(n) 비용)
리사이즈는 가끔 일어나므로 amortized O(1) — 평균적으로는 여전히 O(1)!
Chaining 방식에서 인덱스 3에 이미 "apple"이 있는데 "banana"도 인덱스 3이 나왔습니다. 어떻게 처리하나요?
Load Factor가 1.0을 넘으면 해시 테이블은 더 이상 사용할 수 없다
Python dict가 리사이즈를 시작하는 Load Factor 기준은?
충돌 해결의 두 전략
핵심 용어
해시 충돌
서로 다른 키 → 같은 인덱스를 가리키는 현상
Load Factor
n / m (저장 데이터 수 / 배열 크기)
Chaining
리스트로 연결 — 삭제 쉬움, 메모리 추가 필요
Open Addressing
빈 슬롯 탐색 — 캐시 효율적, 삭제 복잡
Python dict
Open Addressing 변형 사용 (최적화된 프로빙)
정리 노트
해시 충돌 — Chaining vs Open Addressing
충돌 해결 전략
- Chaining
- 같은 인덱스에 연결 리스트 저장 — 구현 간단, 삭제 쉬움
- Open Addressing
- 충돌 시 빈 슬롯 탐색 — 캐시 효율적, 삭제 복잡
- Linear Probing
- 다음 칸으로 이동 (idx+1), 클러스터링 문제 있음
Load Factor
- 공식
- n / m (데이터 수 / 배열 크기)
- 임계값
- Python 2/3, Java 0.75 — 초과 시 리사이즈
- 리사이즈
- 배열 2배 확장 + 전체 재해싱 → amortized O(1)
면접 포인트: 'Chaining vs Open Addressing 차이'는 단골 질문!
시각 자료
핵심 정리
- 1해시 충돌 = 서로 다른 키가 같은 인덱스를 가리킴
- 2Chaining = 리스트 연결, Open Addressing = 빈 슬롯 탐색
- 3Load Factor 관리가 성능의 핵심
퀴즈와 인터랙션으로 더 깊이 학습하세요
play_circle인터랙티브 레슨 시작