해시 테이블은 흔히 해시라고도 불리며, 저장 또는 검색 등에서 자주 사용되는 자료 구조로, 간단하게는 {key: value}의 형태로 데이터를 저장하는 자료 구조이다. 데이터의 검색이나 저장이 빨라 자주 사용되는 자료 구조 중 하나이다.
사실 엄밀히 말하자면 해시는 '입력 데이터인 키(key)가 해시 함수(hash function)에 의해 변환된 일정한 길이의
출력 데이터'를 말하는 것이다. 이처럼 해시 테이블을 구성하는 몇 가지 요소가 있는데, 먼저 그것부터 알아보자.
Direct address table은 키 값을 주소로 사용하는 테이블을 말한다. 만약 (31, 고양이), (560, 강아지)와 같은 데이터가 있다면 '고양이'는 배열의 31번 인덱스에, '강아지'는 배열의 560번 인덱스에 저장하는 것이다. 이러한 자료 구조는 탐색과 삽입, 삭제 등의 연산을 모두 O(1)에 할 수 있지만 다음과 같은 한계점이 있다.
반면 해시 테이블은 키 값을 해시 함수를 통해 특정 크기의 값으로(여기서 특정 크기는 유저가 결정하여 적절한 해시 함수를 사용 혹은 작성하면 된다) 변환하여 사용하므로 효율적으로 배열을 사용할 수 있다. 예컨대 배열의 크기가 100이라고 할 때 키의 값이 31, 560이라면, 해시 함수를 이용해 키 값을 0~100 사이의 값으로 바꾸어 배열에 저장한다. 이렇게 함으로써 데이터 저장 공간을 훨씬 효율적으로 사용할 수 있다.
어떠한 키 값이 해시 함수를 통과했을 때 출력되는 해시가 같은 값이 될 수도 있다.
입력받은 키 값을 100으로 나눈 나머지를 해시로 출력하는 해시 함수를 사용한다고 하자. 이 경우 키가 1인 데이터와 101인 데이터는 모두 1이라는 해시를 갖게 될 것이다.
이처럼 서로 다른 키 값이 해시 함수를 통과했을 때 그 결과가 중복되는 경우를 충돌(Collision)이라 하며, 충돌을 줄여주는 해시 함수가 좋은 해시 함수가 된다. 충돌이 많아질수록 탐색의 시간복잡도가 O(1)에서 O(n)에 가까워지기 때문이다.
해시 테이블에서는 이러한 충돌 문제를 크게 두 가지 방법으로 해결한다.
분리 연결법은 동일한 버킷의 데이터(즉, 충돌이 발생한 데이터)에 대하여 자료 구조(주로 Linked list와 Tree를 사용하며 충돌한 데이터의 개수에 따라 결정한다)를 활용해 추가 메모리를 사용함으로써 다음 데이터의 주소를 저장하는 것이다.
이러한 방식은 해시 테이블의 확장이 필요 없고 구현이 간단하며 삭제 역시 간편하다는 장점이 있다. 그러나 데이터의 수가 많아지면 버킷에 연결되는 데이터가 많아지며, 그에 따라 효율성이 감소한다는 단점이 있다.
이 방법은 분리 연결법과 달리 추가적인 메모리를 사용하지 않고, 비어 있는 해시 테이블의 공간을 활용하는 방법이다. 대표적으로 다음의 3가지 방식을 활용하여 구현한다.
개방 주소법의 경우 별도의 메모리 공간 없이 해시 테이블 내에서 충돌에 대한 처리가 가능해지며, 해시 테이블의 특징인 key-value 간 1:1 매칭 구조가 유지된다는 장점이 있다.
그러나 데이터가 커질수록 버킷 공간 역시 미리 확보해야 하며, 충돌이 많아지면 clustering으로 인해 시간복잡도가 증가하게 된다.
해시 테이블은 유한한 공간에 데이터를 담기 위한 구조이며, 따라서 데이터가 증가하다 보면 버킷이 꽉 차 버릴 수 있다. 분리 연결법을 사용한다면 테이블의 빈 공간이 적어질수록 충돌이 많이 발생하면서 linked list의 길이가 길어져 해시 테이블을 사용하는 의미가 없어지고, 개방 주소법을 사용한다면 테이블이 가득 차는 순간 데이터를 저장할 수 없게 될 것이다.
따라서 데이터가 일정 수준으로 증가하게 되면 버킷의 크기를 증가시켜야 하며, 이를 리사이징이라고 한다. 일반적으로 현재의 데이터가 버킷의 75%를 차지하면 리사이징을 실시하며, 0.75를 load factor라고 한다.