이전 블로그에서 글 옮김
해시테이블은 Key, Value
페어로 데이터를 저장하는 자료구조의 유형 중 하나로 빠르게 데이터를 검색할 수 있는 자료구조 입니다.
해시테이블이 빠른 검색속도를 제공하는 이유는 내부적으로 배열(버킷)을 이용하여 데이터를 저장하기 때문입니다. 해시테이블은 각각의 Key값에
해시함수를 적용해 배열의 고유한 index
를 생성하고 이 index
를 활용하여 값을 저장하거나 검색하게 됩니다. 여기서 실제 값이 저장되는 장소를
버킷 또는 슬롯이라고 합니다.
예를 들어, 우리가 Key, Value
가 'John Smith', '521-1234'
인 데이터를 크기가 16인 해시테이블에 저장한다고 가정해봅시다. 그러면
면저 index = hash_function('John Smith') % 16
연산을 통해 index
값을 계산합니다. 그리고 array[index] = '521-1234'
로
전화번호를 저장하게 됩니다. 이러한 구조로 데이터를 저장하면 Key값으로 데이터를 찾을 때 해시 함수를 한 번만 수행하면 되어 매우 빠르게 데이터를
저장/삭제/조회 할 수 있습니다.
이렇게 index
를 통해 필요한 데이터를 바로 찾을 수 있기 때문에 해시테이블의 평균 시간복잡도는 O(1)
입니다.
해시함수에서 중요한 것은 고유한 인덱스 값을 설정하는 것입니다. 해시테이블에서 사용되는 대표적인 해시함수는 아래의 4가지가 있습니다. 그 중 가장 대표적인것은 Division Method
와 Multiplication Method
입니다.
주소 = 입력값 % 테이블의 크기
로 테이블의 크기를 소수로 정하고 2의 제곱수와 먼 값을 사용해야 효과가 좋다고 알려져 있습니다.해시충돌은 해시테이블의 한 주소를 놓고 두 개 이상의 원소가 충돌하는 것을 말합니다. 쉽게 말하면 해싱을 해서 삽입하려하니 이미 다른 원소가 자리를 차지하고 있는 상황을 말합니다.
해시함수가 무한한 가짓수의 입력값을 받아 유한한 가짓수의 출력값을 생성하는 경우, 비둘기집 원리에 의해 해시충돌은 항상 존재하게 됩니다.
해시충돌을 해결하는 방법은 크게 2가지가 있습니다.
분리 연결법이란 자료를 저장할 때 버킷에서 충돌이 일어나면 해당 값을 기존 값과 연결시키는 방법입니다. 위 사진에서 Sandra를 저장할 때 충돌이 일어났고, 기존에 있던 John에 연결시켰습니다.
이 때 연결리스트(Linked list) 자료구조를 이용합니다. 다음에 저장된 자료를 기존의 자료 다음에 위치시키는 것입니다.
분리 연결법에는 장점과 단점이 존재합니다.
개방 주소법이란 추가적인 메모리를 사용하는 분리 연결법 방식과 다르게 비어있는 해시 테이블의 공간을 활용하는 방법입니다. 개방 주소법을 구현하기 위한 방식에는 대표적으로 3가지가 있습니다.
index
로부터 고정폭만큼 이동하여 차례대로 검색해 비어 있는 버킷에 데이터를 저장하는 방식입니다.각각의 Key값은 해시함수에 의해 고유한 index
를 가지게 되어 바로 접근할 수 있으므로 평균 O(1)의 시간복잡도로 데이터를 조회할 수 있습니다.
하지만 해시충돌이 발생한 경우 체이닝에 연결된 리스트들까지 검색을 해야하기 때문에 최악의 경우 O(n)까지 시간복잡도가 증가할 수 있습니다.
만약 테이블이 꽉 차있는 경우라면 테이블을 확장해야 하는데, 이는 매우 섬각한 성능의 저하를 불러오기 때문에 가급적이면 확장을 하지 않도록 테이블을 설계해야 합니다.
해시테이블의 공간 사용률이 70 ~ 80% 정도가 되면 해시충돌이 빈번하게 발생하여 성능이 저하되기 시작한다고 합니다.
또한 해시테이블에서 자주 사용하게 되는 데이터를 캐시에 적용하면 효율을 높일 수 있습니다. 자주 hit하게 되는 데이터를 캐시에서 바로 찾음으로써 해시테이블의 성능을 향상시킬 수 있습니다.