3 분 소요

1. 해쉬 구조

  • Hash Table: 키(Key)에 데이터(Value)를 저장하는 데이터 구조
    - Key를 통해 바로 데이터를 받아올 수 있으므로, 속도가 획기적으로 빨라짐
    - 파이썬 딕셔너리(Dictionary) 타입이 해쉬 테이블의 예: Key를 가지고 바로 데이터(Value)를 꺼냄
    - 보통 배열로 미리 Hash Table 사이즈만큼 생성 후에 사용
    (공간과 탐색 시간을 맞바꾸는 기법 - 공간을 늘리면 충돌로 인한 추가적인 탐색 시간을 줄일 수 있다)
    - 단, 파이썬에서는 해쉬를 별도 구현할 이유가 없음 - 딕셔너리 타입을 사용하면 됨

2. 알아둘 용어

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

스크린샷 2022-09-09 오후 10 56 53

3. 간단한 해쉬 예

스크린샷 2022-09-09 오후 10 56 28

저장할 data에 대해 key를 추출할 수 있는 별도의 함수가 존재할 수 있다.
key를 해시 함수에 넣으면 해시 주소(=해시 값)를 얻을 수 있다.
얻은 해시 주소에 data를 넣는다.

key를 통해 data를 찾을 때는
마찬가지로 key를 해시 함수에 넣으면 해시 주소(=해시 값)를 얻을 수 있고
해시 테이블에서 해시 주소를 찾아가면 data를 얻을 수 있다.

4. 자료 구조 해쉬 테이블의 장단점과 주요 용도

  • 장점
    - 데이터 저장/읽기 속도가 빠르다. (검색 속도가 빠르다.)
    - 해쉬는 키에 대한 데이터가 있는지(중복) 확인이 쉬움
  • 단점
    - 일반적으로 저장공간이 좀더 많이 필요하다.
    - 여러 키에 해당하는 주소가 동일할 경우 충돌을 해결하기 위한 별도 자료구조가 필요함
  • 주요 용도
    - 검색이 많이 필요한 경우
    - 저장, 삭제, 읽기가 빈번한 경우
    - 캐쉬 구현시 (중복 확인이 쉽기 때문)

5. 프로그래밍 연습

스크린샷 2022-09-09 오후 11 22 21
스크린샷 2022-09-09 오후 11 21 04

6. 충돌(Collision) 해결 알고리즘 (좋은 해쉬 함수 사용하기)

해쉬 테이블의 가장 큰 문제는 충돌(Collision)의 경우이다.
이 문제를 충돌(Collision) 또는 해쉬 충돌(Hash Collision)이라고 부른다.

1) Chaining 기법

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

+) 예시

스크린샷 2022-09-10 오전 2 32 52
위 사진처럼 Andy와 Anthor의 키 값이 각각 11과 21일 때,
해시 함수(key % 5)를 거쳐 나온 해시 주소의 값은 1로 동일할 것이다.

이런 상황을 충돌이 발생했다고 볼 수 있는데,
이 때 해시 테이블의 1번 주소에 [(11(=iKey),…(=value)),(21(=iKey),…(=value))] 처럼
링크드 리스트로 데이터를 뒤에 연결시키는 것이다.

구현

스크린샷 2022-09-10 오전 2 25 45
스크린샷 2022-09-10 오전 2 24 42

테스트

충돌 상황을 테스트 하기 위해서 hashFunc()를 거쳤을 때 같은 해시 주소를 갖는 data를 찾는다.
(해시 주소로 4를 갖는 Daw와 Dlwlrma를 찾을 수 있었다.) 스크린샷 2022-09-10 오전 2 25 07
hashTable을 확인했을 때, 충돌이 생긴 데이터는 리스트 형태로 추가된 것을 확인할 수 있다.

2) Linear Probing 기법

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

구현

스크린샷 2022-09-11 오전 2 39 55
스크린샷 2022-09-11 오전 2 36 12

테스트

마찬가지로 충돌 상황을 테스트 하기 위해서 hashFunc()를 거쳤을 때 같은 해시 주소를 갖는 data를 찾는다.
(해시 주소로 4를 갖는 Daw와 Dlwlrma를 찾을 수 있었다.)
스크린샷 2022-09-11 오전 2 36 26
hashTable을 확인했을 때, 충돌이 발생하지 않은 두 개의 데이터는 hashTable[hashAddr]에 저장되었고,
충돌이 생긴 데이터는 hashTable[hashAddr] 이후 가장 처음 만나는 빈 슬롯에 저장된 것을 확인할 수 있다.

3) 빈번한 충돌을 개선하는 기법

  • 해시 함수을 재정의 및 해시 테이블 저장공간을 확대

기존 hashTable에 8개의 슬롯이 있었을 때, 4개(50%) 이상의 데이터를 넣어야 한다면,
아래와 같이 슬롯의 수를 2배로 늘리고, 그에 따라 hashFunc()도 변경한다.

hashTable = list([None for i in range(16)]) # 기존 8개 -> 16개

def hashFunc(key):
  return key % 16 # 슬롯의 수: 16개

4) 참고: 해시 함수와 키 생성 함수

  • 파이썬의 hash() 함수는 실행할 때마다, 값이 달라질 수 있음
  • 유명한 해쉬 함수들이 있음: SHA(Secure Hash Algorithm, 안전한 해시 알고리즘)
    - 어떤 데이터도 유일한 고정된 크기의 고정값을 리턴해주므로, 해시 함수로 유용하게 활용 가능
    - SHA-1, SHA-256 등을 해시함수로 사용할 수 있다 스크린샷 2022-09-11 오전 3 10 40

연습

스크린샷 2022-09-11 오전 3 22 49

스크린샷 2022-09-11 오전 3 23 25

7. 해쉬 테이블의 시간 복잡도

  • 일반적인 경우(Collision이 없는 경우)는 O(1)
  • 최악의 경우(Collision이 모두 발생하는 경우)는 O(n)

해쉬 테이블의 경우, 일반적인 경우를 기대하고 만들기 때문에, 시간 복잡도는 O(1) 이라고 말할 수 있음

검색에서 해쉬 테이블의 사용 예

  • 16개의 배열에 데이터를 저장하고, 검색할 때 O(n)
  • 16개의 데이터 저장공간을 가진 위의 해쉬 테이블에 데이터를 저장하고, 검색할 때 O(1)


💛 개인 공부 기록용 블로그입니다. 👻

맨 위로 이동하기