Hash Table(해쉬 테이블)
Introduction
- 테이블에 저장되는 데이터는 Key와 Value가 한 쌍을 이룸
- Key가 존재하는 Value는 저장할 수 없으며, 모든 key는 중복되지 않음
- 좋은 해쉬 함수는 '충돌을 덜 일으키는 해쉬 함수'
- 일반적인 해쉬 함수(remainder method)의 개선안
- 자릿수 폴딩(Folding Method): 자릿수를 균등한 조각으로 나누어 각각의 값을 더한 후, 그 값의 remainder를 key값으로 설정
- ex) 436-555-4601이라는 값을 두 자리로 쪼깨어 더함
- 43 + 65 + 55 + 46 + 01 = 210
- 210 % 11 = 1으로 1의 key값을 가지게 됨
- 각각의 조각을 reverse하여 더하는 folding method도 존재
- ex) 43 + 56 + 55 + 64 + 01 = 219
- 중간 제곱법(Mid-Square Method): Value를 제곱한 후 가운데 값의 remainder를 key값으로 설정
- ex) 44 ^ 2 = 1,936
- 93 % 11 = 5로 5의 key값을 가지게 됨
- Ordinal Value의 활용: 문자열을 구성하는 각각의 문자의 ASCII 값을 모두 더해 나온 결과의 remainder를 key 값으로 설정하는 방법
- ex) 'cat' = 99 + 97 + 116 = 312
- 312 % 11 = 4로 4의 key값을 지니게 됨
- 그러나 이는 anagram의 단어들이 모두 같은 값을 지니게 된다는 단점이 있음
- 해결 방안: weighting factor의 추가
- 'cat' = 99*1 + 97*2 + 116*3
- 자릿수 폴딩(Folding Method): 자릿수를 균등한 조각으로 나누어 각각의 값을 더한 후, 그 값의 remainder를 key값으로 설정
- 충돌 문제의 해결안
-
Open Addressing: 해쉬 테이블에서 다음 open된 slot을 찾는 방법
- 선형 조사법(Linear Probing): hash function에 의해 저장되어야 하는 slot의 다음 slot들을 차례로 확인하며 빈 slot을 찾아가는 방법
- ex) 77, 44, 55는 모두 0번 index에 들어가야 하는 value들
- 그러나 77이 0번 index를 차지하고 있기 때문에 44는 1번 index에, 55는 0, 1번 index가 꽉찬 것을 확인하고 비어있는 2번 index에 저장하는 방법
- 선형 조사법의 단점은 Clustering이 생긴다는 것
- 즉, slot들을 골고루 사용하는 것이 아닌 특정 지역의 slot들만 과도하게 사용되는 경향이 생김
- 이차 조사법(Quadratic Probing): hash function에 의해 저장되어야 하는 slot 위치에 + 1, + 4, + 9 등을 더해 slot들이 고르게 분배되도록 하는 방법
- ex) 77, 44, 55의 value들을 저장해야 함
- 77은 0번 index에 44는 0 + 1으로 1번 index에 그리고 55는 0 + 4로 4번 index에 저장시키게 됨
- 선형 조사법(Linear Probing): hash function에 의해 저장되어야 하는 slot의 다음 slot들을 차례로 확인하며 빈 slot을 찾아가는 방법
-
Close Addressing(Chaining): 각각의 slot에 item들이 여러 개 저장될 수 있도록 구현하는 방법
해쉬 테이블 구현에 필요한 기본 연산
- HashTable(): 새로운 HashTable을 생성
- put(key, val): key와 value을 쌍으로 HashTable에 추가함. Key 값이 이미 존재하면 old value를 new value로 변경
- get(key): key가 주어지면 그에 맞는 value값을 반환
- del: Key 값을 이용하여 Key-Value 쌍을 삭제
- len(): Key-Value 쌍의 크기 반환
- in: HashTable에 Key 값이 있는지의 여부를 반환
해쉬 테이블의 로직
class HashTable:
def __init__(self):
self.size = 11
self.slots = [None] * self.size
self.data = [None] * self.size
- Key 값들을 담는 slots라는 list 생성
- slots라는 list와 평행하게 존재하며, value를 담을 data list 생성
- HashTable의 크기는 소수로 설정하는 것이 충돌 문제 해결에 효과적
def put(self, key, value):
hashval = self.hashfunction(key, len(self.slots))
if self.slots[hashvalue] == None:
self.slot[hashvalue] = key
self.data[hashvalue] = data
else:
# original key값이 같을 경우 replace
if self.slots[hashvalue] == key:
self.data[hashvalue] = data
else:
nextslot = self.rehash(hashvalue, len(self.slots))
# slot이 차있고, original key값과 rehash 값이 다르면 계속 rehash
while self.slots[nextslot] != None and \
self.slots[nextslot] != key:
nextslot = self.rehash(nextslot, len(self.slots))
# slot이 비어있으면 slot에 key와 data 입력
if self.slots[nextslot] == None:
self.slots[nextslot] = key
self.data[nextslot] = data
# rehash 값과 original key 값이 같을 경우 replace
# 즉, rehash 통해 이미 자리한 경우
else:
self.data[nextslot] = data
def hashfunction(self, key, size):
return key % size
# Linear Probing 사용
def rehash(self, oldhash, size):
return (oldhash+1) % size
- nextslot의 rehash를 탈출하는 조건은 2가지로 분류
- rehash를 통해 얻은 slot이 비어있는 경우: 빈 자리에 key와 data를 삽입
- rehash를 통해 얻은 slot의 key값과 입력 값의 key 값이 같은 경우: value를 replace
- rehash function으로는 Linear Probing 사용
def get(self, key):
startslot = self.hashfunction(key, len(self.slot))
data = None
stop = False
found = False
position = startslot
while self.slots[position] != None and
not found and not stop:
# 값을 찾은 경우 while문 탈출
if self.slots[position] == key:
found = True
data = self.data[position]
else:
position = self.rehash(position, len(self.slots))
# 최초의 slot으로 돌아오지 않도록 제어
if poisition == startslot:
stop = True
return data
def __getitem__(self, key):
return self.get(key)
def __setitem__(self, key, data):
self.put(key, data)
- position의 rehash를 탈출하는 조건은 3가지로 분류
- rehash를 통해 얻은 position 값이 입력 값의 key 값과 일치하는 경우: 검색에 성공한 case
- rehash를 통해 얻는 position 값이 hashfucntion으로 얻은 값과 일치하는 경우: 원값으로 돌아올 때 까지 탐색에 실패한 case
- rehash를 통해 얻은 position 값의 slot이 비어있는 경우: Linear Search를 하다가 값이 존재하지 않는 case
- [] 연산의 사용을 위해 __getitem__과 __setitem__ method를 overloading
전체 코드
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | class HashTable: def __init__(self): self.size = 11 self.slots = [None] * self.size self.data = [None] * self.size def put(self, key, data): hashvalue = self.hashfunc(key, len(self.slots)) if self.slots[hashvalue] == None: self.slots[hashvalue] = key self.data[hashvalue] = data else: if self.slots[hashvalue] == key: self.data[hashvalue] = data else: nextslot = self.rehash(hashvalue, len(self.slots)) while self.slots[nextslot] != None and \ self.slots[nextslot] != key: nextslot = self.rehash(nextslot, len(self.slots)) if self.slots[nextslot] == None: self.slots[nextslot] = key self.data[nextslot] = data else: self.data[nextslot] = data def hashfunc(self, key, size): return key % size def rehash(self, oldhash, size): return (oldhash+1) % size def get(self, key): startslot = self.hashfunc(key, len(self.slots)) data = None stop = False found = False position = startslot while self.slots[position] != None and \ not found and not stop: if self.slots[position] == key: found = True data = self.data[position] else: position = self.rehash(position, len(self.slots)) if position == startslot: stop = True return data def __getitem__(self, key): return self.get(key) def __setitem__(self, key, data): self.put(key, data) | cs |
[Reference]
Problem Solving with Algorithms and Data Structure
윤성우의 열혈 자료구조
'Software Convergence > Data Structure' 카테고리의 다른 글
Chaining을 이용하는 Hash Table의 구현 (0) | 2018.08.27 |
---|---|
Priority Queue(우선순위 큐)의 구현 (0) | 2018.08.26 |