🌈 해쉬 테이블(Hash Table)
🔥 해쉬 테이블이란?
🔥 python 내장 함수 hash
🔥 Hash 충돌 해결
1.해쉬 테이블이란?
- 해쉬 테이블은 키(Key)에 데이터를(Value)를 저장하는 데이터 구조로 키(Key)를 통해 데이터를 받아올 수 있기 때문에 속도가 빠름
- python에서는 해쉬를 별도 구현할 필요없이 딕셔너리를 사용할 수 있고, 딕셔너리도 내부적으로 해쉬 테이블의 구조를 띄고 있음
- 해쉬 테이블 관련 용어
- 해쉬 : 임의에 값을 고정 길이로 변환하는 것
- 해쉬 테이블 : 키 값의 연산에 의해 직접 접근이 가능한 데이터 구조
- 해싱 함수 : Key에 대해 산술 연산을 적용해 해쉬 값으로 변환해주는 함수
- 해쉬 값(해쉬 주소) : Key를 해싱 함수에 대입하여 반환되는 것을 해쉬 값이라하고, 이를 기반으로 테이블에서 해당 Key에 대한 데이터 위치를 탐색
- 슬롯 : 한개의 데이터를 저장할 수 있는 공간
1) 간단 Hash Table 만들기
hash_table = list([0 for i in range(10)])
print(hash_table)
def hash_func(key):
return key % 5
def storage_data(data, value):
key = ord(data[0])
hash_address = hash_func(key)
hash_table[hash_address] = value
storage_data('Jaewon', '010-1234-5678')
storage_data('Haezin', '010-4444-9999')
storage_data('Ginsu', '010-8989-1212')
print(hash_table)
def get_data(data):
key = ord(data[0])
hash_address = hash_func(key)
return hash_table[hash_address]
print(get_data('Jaewon'))
print(get_data('Haezin'))
print(get_data('Ginsu'))
2) Hash Table 특징
- 장점 : 데이터 저장/읽기 속도 빠름, 해쉬는 키에 대한 데이터의 존재여부 확인 및 중복 처리가 쉬움
- 단점 : 불필요한 저장 공간을 확보해두기 때문에 비교적 저장 공간이 많이 필요, 여러 키에 해당하는 주소가 동일할 경우 충돌을 막기 위한 자료구조가 별도로 필요
- 주요 용도 : 검색이 많이 필요한 경우, 저장&삭제&읽기가 빈번한 경우, 캐쉬 구현시
2. python 내장 함수 hash
- python에서는 hash라는 내장함수를 제공하고, 파라미터를 주면 자동으로 키를 생성함
print(hash('jaewon'))
hash_table = list([0 for i in range(8)])
def get_key(data):
return hash(data)
def hash_function(key):
return key % 8
def save_data(data, value):
hash_address = hash_function(get_key(data))
hash_table[hash_address] = value
def read_data(data):
hash_address = hash_function(get_key(data))
return hash_table[hash_address]
save_data('Jaewon', '01011112222')
save_data('Haezin', '01088889999')
print(hash_table)
print(read_data('Jaewon'))
print(read_data('Haezin'))
3. Hash 충돌 해결
- 0부터 100까지 수를 key라 하고, 8로 나눈 나머지를 구하는 hash_function에 넣어 나올 수 있는 모든 hash address는 0~7이기 때문에 중복이 발생
- 이로 인해 hash의 주소가 동일해 데이터가 덮어씌어져버리는 문제가 발생
l = [i for i in range(100)]
data_set = []
for i in l:
data = i % 8
data_set.append(data)
print(list(set(data_set)))
1) Chaining 기법
- Open Hashing 기법 중 하나로 해쉬 테이블 공간 외의 공간을 마련하여 데이터를 저장함
- 즉, 충돌이 발생 시 연결 리스트 자료 구조를 사용해 데이터를 저장하는 기법
- 단, 공간이 많이 필요함
- Chaining 기법은 데이터를 식별할 수 있는 key값을 함께 저장하여 이를 식별함
- 🔍 index_key = get_key(data)
hash_table = list([0 for i in range(8)])
def get_key(data):
return hash(data)
def hash_function(key):
return key % 8
def save_data(data, value):
index_key = get_key(data)
hash_address = hash_function(index_key)
if hash_table[hash_address] != 0:
for index in range(len(hash_table[hash_address])):
if hash_table[hash_address][index][0] == index_key:
hash_table[hash_address][index][1] = value
return
hash_table[hash_address].append([index_key, value])
else:
hash_table[hash_address] = [[index_key, value]]
def read_data(data):
index_key = get_key(data)
hash_address = hash_function(index_key)
if hash_table[hash_address] != 0:
for index in range(len(hash_table[hash_address])):
if hash_table[hash_address][index][0] == index_key:
return hash_table[hash_address][index][1]
return None
else:
return None
print(hash('Jaewon')%8)
print(hash('Haezin')%8)
print(hash('AaLa')%8)
save_data('Jaewon', '01011112222')
save_data('Haezin', '01088889999')
save_data('AaLa', '01033331234')
print(hash_table)
print(read_data('Jaewon'))
print(read_data('Haezin'))
2) Linear Probing 기법
- Close Hashing 기법 중 하나로 해쉬 테이블 저장 공간 안에서 충돌 문제를 해결하는 기법으로 Linear Probing 기법이 대표적임
- Linear Probing 기법은 충돌 발생 시, hash adress의 다음 adress부터 맨 처음 나오는 빈공간에 저장하는 방식이기 때문에 저장공간 활용도가 높음
hash_table = list([0 for i in range(8)])
def get_key(data):
return hash(data)
def hash_function(key):
return key % 8
def save_data(data, value):
index_key = get_key(data)
hash_address = hash_function(index_key)
if hash_table[hash_address] != 0:
for index in range(hash_address, len(hash_table)):
if hash_table[index] == 0:
hash_table[index] = [index_key, value]
return
elif hash_table[index][0] == index_key:
hash_table[index][1] = value
else:
hash_table[hash_address] = [index_key, value]
def read_data(data):
index_key = get_key(data)
hash_address = hash_function(index_key)
if hash_table[hash_address] != 0:
for index in range(hash_address, len(hash_table)):
if hash_table[index] == 0:
return None
elif hash_table[index][0] == index_key:
return hash_table[index][1]
else:
return None
print(hash('Jaewon')%8)
print(hash('Haezin')%8)
print(hash('AaLa')%8)
save_data('Jaewon', '01011112222')
save_data('Haezin', '01088889999')
save_data('AaLa', '01033331234')
print(hash_table)
print(read_data('Jaewon'))
print(read_data('Haezin'))
- 빈번한 hash 충돌이 나타난다면, 해쉬 함수를 재정의하거나 해쉬 테이블 저장 공간 확대해야 함
3) hash table 시간 복잡도
- 충돌이 없는 경우 : O(1)
- 충돌이 발생하는 경우 : O(N)