FKS 해싱
IT 위키
FKS 해싱(FKS Hashing)은 정적인 키 집합에 대해 O(1) 시간의 탐색을 보장하는 두 단계 해싱(two-level hashing) 기법이다. 이 방법은 Fredman, Komlós, Szemerédi 세 명의 연구자가 제안하였으며, 완전 해싱(perfect hashing)의 대표적 구현으로 널리 알려져 있다.
- 최적 정적 해싱(Optimal Static Hashing)이라고도 불린다.
1 개요[편집 | 원본 편집]
FKS 해싱은 다음과 같은 조건에서 동작하도록 설계된다:
- 키 집합이 고정(static)되어 있으며, 삽입/삭제는 없다고 가정
- 각 검색 연산이 최악의 경우에도 O(1) 시간에 완료됨
- 기대 공간 복잡도는 O(n)이며, 간단한 무작위 해시 함수(universal hashing)를 사용
전체 구조는 다음과 같이 구성된다.
2 알고리즘 구조[편집 | 원본 편집]
2.1 1단계 해싱[편집 | 원본 편집]
- 해시 함수 h: U → {0, 1, ..., m−1} 를 사용하여 키 집합 S의 각 키를 m개의 버킷(bucket)으로 분할함
- 보통 m = n (키 개수)로 설정하여 각 슬롯에 평균적으로 1개의 키가 들어가도록 함
2.2 2단계 해싱[편집 | 원본 편집]
- 각 슬롯 i에 대해서 별도의 해시 테이블 Ti를 구성
- Ti의 크기는 해당 슬롯의 키 개수 ki의 제곱, 즉 ki²
- 무작위 해시 함수 hi를 이용해 Ti 내에서 충돌 없이 키를 배치 (완전 해싱)
3 동작 예시[편집 | 원본 편집]
고정된 키 집합이 다음과 같다고 가정하자:
S = {3, 8, 11, 14, 18, 19, 21, 24, 27, 30} → n = 10, 따라서 m = 10 (1단계 테이블 크기)
3.1 1단계: h(x) = x mod 10[편집 | 원본 편집]
각 키를 아래와 같이 슬롯에 배치:
슬롯 번호 (i) | 키 목록 |
---|---|
0 | 30 |
1 | 11, 21 |
2 | 2 (없음) |
3 | 3 |
4 | 14, 24 |
5 | 5 (없음) |
6 | 6 (없음) |
7 | 27 |
8 | 8, 18 |
9 | 19 |
이제 각 슬롯에 대해 2차 해시 테이블 구성
3.2 2단계 예시: 슬롯 1 (키: 11, 21)[편집 | 원본 편집]
- 키 개수 k = 2 → 보조 해시 테이블 크기 = 2² = 4
- 무작위로 h1(x) = (a·x + b) mod p mod 4 라는 형식의 해시 함수 선택
- 예: h1(11) = 0, h1(21) = 2 → 충돌 없음 → 이 해시 함수 채택
다른 슬롯들에도 동일 방식으로 적용하여 모두 충돌 없이 배치 가능하도록 무작위 함수를 반복 시도
4 FKS 해싱 Python 예제[편집 | 원본 편집]
아래는 간단한 FKS 해싱 구조를 파이썬으로 구현한 예제이다. 이 코드는 1단계 해시와 2단계 보조 테이블을 구성하며, 보조 해시 함수는 충돌이 없도록 반복 시도하여 선택한다.
import random
# 유니버설 해시 함수 생성기
def generate_hash_function(p, m):
a = random.randint(1, p - 1)
b = random.randint(0, p - 1)
return lambda x: ((a * x + b) % p) % m
# 2차 해싱: 완전 해시 테이블 생성
def build_secondary_table(keys, p):
n = len(keys)
if n == 0:
return None, None
size = n * n
while True:
h = generate_hash_function(p, size)
table = [None] * size
collision = False
for key in keys:
idx = h(key)
if table[idx] is not None:
collision = True
break
table[idx] = key
if not collision:
return h, table
# FKS 해싱 전체 구성
def build_fks_table(keys):
p = 1000003 # 큰 소수
n = len(keys)
h1 = generate_hash_function(p, n)
# 1단계 테이블 구성
buckets = [[] for _ in range(n)]
for key in keys:
idx = h1(key)
buckets[idx].append(key)
# 2단계 보조 테이블 구성
second_level = []
for bucket in buckets:
h2, table = build_secondary_table(bucket, p)
second_level.append((h2, table))
return h1, second_level
# 탐색 함수
def fks_lookup(h1, second_level, key):
idx = h1(key)
h2, table = second_level[idx]
if h2 is None:
return False
pos = h2(key)
return table[pos] == key
# 예제 실행
keys = [3, 8, 11, 14, 18, 19, 21, 24, 27, 30]
h1, second_level = build_fks_table(keys)
# 검색 테스트
test_keys = [14, 22, 30, 7]
for k in test_keys:
result = fks_lookup(h1, second_level, k)
print(f"Key {k} found? {result}")
출력 예시
Key 14 found? True
Key 22 found? False
Key 30 found? True
Key 7 found? False
이 코드는 정적 키 집합을 기반으로 FKS 해싱 구조를 구성하고, 주어진 키가 존재하는지 O(1) 시간에 탐색할 수 있는지를 테스트한다. 2단계 해시는 충돌이 없는 해시 함수를 무작위로 선택하여 반복 구성하며, 평균적으로 빠르게 종료된다.
5 공간 분석[편집 | 원본 편집]
- 1단계 테이블 크기: n
- 2단계 테이블의 총 공간은 각 슬롯 i에 대해 ki²의 합으로 계산됨
- 이 합의 기대값은 O(n)임이 증명되어 있음
6 시간 복잡도[편집 | 원본 편집]
- 초기 구성 시간: 기대 O(n)
- 탐색: 최악의 경우에도 O(1)
- 삽입/삭제는 정적 구조에서는 지원하지 않음
7 장점[편집 | 원본 편집]
- 매우 빠른 탐색 (O(1), 최악의 경우 포함)
- 정적 집합에서 완전 해싱 구현 가능
- 해시 함수 선택 실패 시에도 재시도로 성공 가능 (확률적으로 보장됨)
8 단점[편집 | 원본 편집]
- 정적 집합에만 적합 (삽입/삭제가 없는 경우)
- 초기 설정이 다소 복잡하고 메모리 낭비 가능성 존재 (k² 사용)
9 같이 보기[편집 | 원본 편집]
10 참고 문헌[편집 | 원본 편집]
- Fredman, M. L., Komlós, J., & Szemerédi, E. (1984). Storing a sparse table with O(1) worst case access time. Journal of the ACM, 31(3), 538–544.
- Cormen, T. H., Leiserson, C. E., Rivest, R. L., & Stein, C. (2009). Introduction to Algorithms. MIT Press.