Python/알고리즘&자료구조

Python 자료구조&알고리즘 - 해쉬 테이블(Hash Table)

code2772 2023. 1. 30. 09:48
728x90
반응형
 

 

1. 해쉬 테이블(Hash Table)

  • 키(key)에 데이터(value)를 저장하는 데이터 구조
  • 파이썬에서는 딕셔너리(dick)타입이 해쉬 테이블의 예
  • key를 통해 데이터를 바로 찾을 수 있으므로 검색 속도가 빠름
  • 보통 배열로 미리 Hash Table 사이즈 만큼 생성 후에 사용
 

2. 알아둘 용어

  • 해쉬(Hash) : 임의의 값을 고정 길이로 변환하는 것
  • 해쉬 테이블(Hash Table): 키 값의 연산에 의해 직접 접근이 가능한 데이터 구조
  • 해쉬 함수(Hashing Function) : key에 대해 산술 연산을 이용해 데이터 위치를 찾을 수 있는 함수
  • 해쉬 값(Hash Values) 또는 해쉬 주소(Hash Address) : key를 해싱 하무로 연산해서 해쉬 값을 알아내고 이를 기반으로 해쉬 테이블에 해당 key에 대한 데이터 위치를 일관성 있게 찾음
  • 슬롯(Slot) : 한 개의 데이터를 저장할 수 있는 공간
 

3. 간단한 해쉬 예

 

3-1. 슬롯 만들기

 

 

[ ]
 
hash_table = list([0 for i in range(10)])# 10개의 칸으로 만들고 프린트
print(hash_table)
 
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
 

3-2. 해쉬 함수 만들기

  • 해쉬 함수는 다양하게 생성할 수 있으며 가장 간단한 방법으로 division법 (나누기를 통한 나머지 값을 사용하는 기법)을 사용해봄
 

[ ]
 
def hash_func(key):
  return key % 10
 
 

 


3-3. 해쉬 테이블에 저장하기

  • 데이터에 따라 필요시 key 생성 방법 정의가 필요함
 

[ ]
 
data1 = 'apple'
data2 = 'banana'
data3 = 'orange'
data4 = 'melon'
 
 

[ ]
 
# ord() : 문자의 아스키코드를 반환
print(ord(data1[0]))
print(ord(data2[0]))
print(ord(data3[0]))
print(ord(data4[0]))
 
97
98
111
109

 


[ ]
 
print(hash_func(ord(data1[0])))
print(hash_func(ord(data2[0])))
print(hash_func(ord(data3[0])))
print(hash_func(ord(data4[0])))
 
7
8
1
9

[ ]
 
def storage_data(data, value):
  key = ord(data[0])
  hash_address = hash_func(key)
  hash_table[hash_address] = value
 
 

[ ]
 
storage_data('apple','010-1111-1111')
 
 

[ ]
 
hash_table
 
[0, 0, 0, 0, 0, 0, 0, '010-1111-1111', 0, 0]
 

[ ]
 
storage_data('banana','010-2222-2222')
storage_data('orange','010-3333-3333')
storage_data('melon','010-4444-4444')
 
 

[ ]
 
hash_table
 
[0,
 '010-3333-3333',
 0,
 0,
 0,
 0,
 0,
 '010-1111-1111',
 '010-2222-2222',
 '010-4444-4444']
 

3-4. 해쉬 함수를 사용해서 해쉬함수를 수정하기

 

[ ]
 
hash('apple')
 
-3756472184613635755
 

[ ]
 
hash('apple')
 
-3756472184613635755
 

[ ]
 
hash('banana')
 
754177351106668074
 

[ ]
 
def get_key(data):
  return hash(data)

def hash_function(key):
  return key % 10

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]
 
 

[ ]
 
hash_table = list([0 for i in range(10)])# 10개의 칸으로 만들고 프린트
print(hash_table)
 
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
 

[ ]
 
save_data('apple', '010-1111-1111')
 
 

[ ]
 
hash_table
 
[0, 0, 0, 0, 0, '010-1111-1111', 0, 0, 0, 0]
 

[ ]
 
read_data('apple')
 
 
 

4. 해쉬 테이블의 장단점

  • 장점 *데이터 저장 및 읽기 속도가 빠름(검색 속도가 빠름)
    • 해쉬는 키에 대한 데이터가 있는지 확인 쉬움
  • 단점
    • 저장공간이 많이 필요함
    • 여러키에 해당하는 주소가 동일한 경우 충돌을 해결하기 위한 별도의 자료구조가 필요함
 

5. 충돌(Collision) 해결 알고리즘

 

 

 

5-1. Linear Probling 기법

  • 패쇄 해싱 또는 clos Hashing 기법 중 하나
  • 해쉬 테이블 저장공간 안에서 충돌 문제를 해결하는 기법
  • 충돌이 일어나면 해당 hash address의 다음 address 부터 맨 처음 나오는 빈공간에 저장하는 기법
  • 저장공간 활용도를 높이기 위한 방법

 

hash_table = list([0 for i in range(10)])# 10개의 칸으로 만들고 프린트
print(hash_table)

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: # 충돌이냐?? 아닐경우 else로 이동
      # 충돌인 경우 내가 넣을려는 인덱스와 -> 하단으로 이동
        for index in range(hash_address, len(hash_table)):# 충돌난 곳에서 부터 마지막까지 돌아라
            if hash_table[index] == 0:# 0이니??
                hash_table[index] = [index_key, value]
                return
            elif hash_table[index][0] == index_key:# key값이 같니??
                hash_table[index][1] = value
                return
    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('apple')%8)
print(hash('avocado')%8)
print(hash('cherry')%8)
print(hash('banana')%8)
print(hash('orange')%8)
print(hash('mellon')%8)

save_data('orange', '010-3333-3333')
save_data('mellon', '010-4444-4444')
save_data('banana', '010-2222-2222')

hash_table

read_data('orange')

read_data('melon')

save_data('melon', '010-4444-4444')

hash_table



5-2. Chaining 기법

  • 개방 해쉬 또는 Open hashing 기법중 하나
  • 해쉬 테이블 저장공간 외의 공간을 활용하는 방법
  • 충돌이 일어나면 링크드 리스트 자료구조를 사용해서 링크드 리스트로 데이터를 추가로 뒤에 연결시켜 저장하는 방법

6. 해쉬 함수와 키 생성 함수

  • SHA(Secure Hash Algorithm, 안전한 해쉬 알고리즘) 와 같은 유명한 해쉬 알고리즘을 많이 사용
  • 어떤 데이터도 유일한 고정된 크기의 고정값을 리턴해주므로 해쉬 함수로 유용하게 활용할 수 있음
 

6-1. SHA-1

  • 임의의 길이의 입력데이터 최대 160 비트(16진수 40자리)의 출력데이터(해시값)로 바꿈
  • 파이썬의 hash() 함수는 환경에 따라 값이 매번 달라질 수 있음


import hashlib
data = 'test'.encode() # test 문자열을 바이트 단위로 바꿈
print(data)
hash_object = hashlib.sha1()
print(hash_object)
hash_object.update(data) # sh-1 객체로 data를 읽어옴
hex_dig = hash_object.hexdigest() # 16진수로 고정된 해쉬 값을 발생
print(hex_dig)
print(int(hex_dig, 16)) # 16진수로 고정된 해쉬값을 10진수의 고정된 해쉬값으로 반환

### 6-2. SHA-256
* SHA 알고리즘의 한 종류로 256비트로 구성되어 64자리 문자열을 반환
* SHA-2 계열 중 하나이며 블록체인에서 가장 많이 체택하여 사용

import hashlib # hash라이브러리
data = 'test'.encode() 
print(data)
hash_object = hashlib.sha256()
print(hash_object)
hash_object.update(data) 
hex_dig = hash_object.hexdigest() 
print(hex_dig)
print(int(hex_dig, 16))​


### 문제
chaining 기법을 적용한 해쉬 테이블 코드에 키 생성함수 sha2 해쉬 알고리즘을 사용하도록 변경해보자
1. 해쉬 함수 : key%10
2. 해쉬 키 생성 : sha256(data)

import hashlib # hash라이브러리

hash_table = list([0 for i in range(10)])

def get_key(data):
 hash_object = hashlib.sha256()
 hash_object.update(data.encode())
 hex_dig = hash_object.hexdigest() 
 return int(hex_dig, 16)

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

save_data('orange', '010-3333-3333')
save_data('mellon', '010-4444-4444')
save_data('banana', '010-2222-2222')
save_data('apple', '010-44433-3333')
save_data('cherry', '010-4664-4444')
save_data('avocado', '010-7777-7777')

hash_table

print(hash('apple')%10)
print(hash('avocado')%10)
print(hash('cherry')%10)
print(hash('banana')%10)
print(hash('orange')%10)
print(hash('mellon')%100)

read_data('avocado')​



반응형