-
자료구조 해쉬 테이블3자료구조와 알고리즘 2020. 10. 25. 17:37
1. 충돌(Collision) 해결 알고리즘 (좋은 해쉬 함수 사용하기)
해쉬 테이블의 가장 큰 문제는 충돌(Collision)의 경우입니다. 이 문제를 충돌(Collision) 또는 해쉬 충돌(Hash Collision)이라고 부릅니다.
*충돌(Collision) : 한개 이상의 데이터가 동일한 해쉬어드레스에 저장이 되어야 할 경우6.1. Chaining 기법
- 개방 해슁 또는 Open Hashing 기법 중 하나: 해쉬 테이블 저장공간 외의 공간을 활용하는 기법
- 충돌이 일어나면, 링크드 리스트라는 자료 구조를 사용해서, 링크드 리스트로 데이터를 추가로 뒤에 연결시켜서 저장하는 기법
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
* hash(값) << 파이썬 내장함수 값을 숫자값으로 리턴 (매번 다름)
print (hash('Dave') % 8) print (hash('Dbb') % 8) print (hash('Data') % 8)
1
3
3
save_data('Dbb', '123') save_data('Data', '456') read_data('Data')
'456'
hash_table
6.2. Linear Probing 기법
- 폐쇄 해슁 또는 Close Hashing 기법 중 하나: 해쉬 테이블 저장공간 안에서 충돌 문제를 해결하는 기법
- 충돌이 일어나면, 해당 hash address의 다음 address부터 맨 처음 나오는 빈공간에 저장하는 기법
- 저장공간 활용도를 높이기 위한 기법
링크드 리스트로 값을 저장하는 것이 아니라 다음 빈 공간에 값을 넣는다.
연습3: 연습1의 해쉬 테이블 코드에 Linear Probling 기법으로 충돌해결 코드를 추가해보기
1. 해쉬 함수: key % 8
2. 해쉬 키 생성: hash(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(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 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
6.3. 빈번한 충돌을 개선하는 기법
최대한 충돌이 일어나지 않게 하는것이 해쉬코드를 쓰는 이유
- 해쉬 함수을 재정의 및 해쉬 테이블 저장공간을 확대
- 예:
hash_table = list([None for i in range(16)]) def hash_function(key): return key % 16
공간을 늘리면 충돌이 줄어들어 탐색시간도 줄어드는 기법이다.
참고: 해쉬 함수와 키 생성 함수
- 파이썬의 hash() 함수는 실행할 때마다, 값이 달라질 수 있음
- 유명한 해쉬 함수들이 있음: SHA(Secure Hash Algorithm, 안전한 해시 알고리즘)
- 어떤 데이터도 유일한 고정된 크기의 고정값을 리턴해주므로, 해쉬 함수로 유용하게 활용 가능
- SHA-256은 실제로 블록체인에서 사용하는 기법
SHA-1 사용방법
* hashlib이 동작이 안된다면 pip install hashlib
import hashlib #'test'라는 데이터를 String sha1()알고리즘을 쓰고싶다 data = 'test'.encode() hash_object = hashlib.sha1() #encode()는 값을 Byte로 바꾼다는 소리 #값을 바이트로 바꾼다음에 update를 해줘야 hash값이 추출이된다. hash_object.update(data) #or update(b'test') #hash_object를 몇진수로 추출하고 싶으냐hexdigest (여기선 16) hex_dig = hash_object.hexdigest() print (hex_dig)
a94a8fe5ccb19ba61c4c0873d391e987982fbbd3 << 'test'의 hash값을 16진수로 한것이 이값
SHA-256 사용방법
import hashlib data = 'test'.encode() hash_object = hashlib.sha256() hash_object.update(data) hex_dig = hash_object.hexdigest() print (hex_dig)
9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08
* 출력된 값으로 어떤 값을 입력한건지 추론은 불가능
어떤 데이터가 오던간에 고정된 길이로 일관된 값이 나온다.
연습4: 연습2의 Chaining 기법을 적용한 해쉬 테이블 코드에 키 생성 함수를 sha256 해쉬 알고리즘을 사용하도록 변경해보기
1. 해쉬 함수: key % 8
2. 해쉬 키 생성: hash(data)import hashlib hash_table = list([0 for i in range(8)]) def get_key(data): hash_object = hashlib.sha256() hash_object.update(data.encode()) hex_dig = hash_object.hexdigest() # hex_dig 이 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08 # 와 같이 16진수 문자열이기 때문에 key % 8을 할수가 없다. # 그래서 int 로 바꾸고 16진수의 문자열을 10진수 정수로 만든다는 뜻 return int(hex_dig, 16) 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 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
7. 시간 복잡도
- 일반적인 경우(Collision이 없는 경우)는 O(1)
- 최악의 경우(Collision이 모두 발생하는 경우)는 O(n)
해쉬 테이블의 경우, 일반적인 경우를 기대하고 만들기 때문에, 시간 복잡도는 O(1) 이라고 말할 수 있음
검색에서 해쉬 테이블의 사용 예
- 16개의 배열에 데이터를 저장하고, 검색할 때 O(n) << 배열의 사이즈에 따라 검색하고 순회를 해야한다.
- 16개의 데이터 저장공간을 가진 위의 해쉬 테이블에 데이터를 저장하고, 검색할 때 O(1)
저장과 검색에 있어서 굉장히 효율적인 자료 구조이다.
'자료구조와 알고리즘' 카테고리의 다른 글
자료구조 트리2 (0) 2020.10.26 자료구조 트리 (0) 2020.10.26 자료구조 해쉬 테이블2 (0) 2020.10.25 자료구조 해쉬 테이블 (0) 2020.10.23 시간 복잡도 (0) 2020.10.23