2-4. 자료구조: 해시 테이블
1. 해시 테이블의 기본 개념
자료구조는 데이터를 효율적으로 저장하고 관리하기 위한 틀입니다. 배열, 연결 리스트, 스택, 큐 등 다양한 자료구조가 존재하며, 각 자료구조는 데이터를 저장하고 접근하는 방식에 따라 장단점을 가집니다. 해시 테이블(Hash Table)은 이러한 자료구조 중에서도 특히 빠른 탐색(search), 삽입(insert), 삭제(delete)을 지원하는 자료구조입니다. 해시 테이블은 실생활에서 흔히 볼 수 있는 사전(dictionary)과 유사한 방식으로 작동합니다. 즉, key-value 쌍으로 데이터를 저장하고, key를 사용하여 value에 빠르게 접근할 수 있도록 설계되었습니다.
1) 사전(Dictionary)의 비유
사전을 생각해 봅시다. 단어를 찾기 위해 사전 전체를 처음부터 끝까지 일일이 찾아보는 것은 매우 비효율적입니다. 대신, 사전은 알파벳 순서대로 정렬되어 있어서, 찾는 단어의 첫 글자를 기준으로 빠르게 해당 페이지를 찾아갈 수 있습니다. 해시 테이블도 이와 유사한 방식으로 작동합니다.
- Key: 사전에서 단어 (예: "apple", "banana")
- Value: 단어의 뜻, 설명 (예: 사과, 바나나)
해시 테이블은 키를 사용하여 해당 키에 해당하는 값을 빠르게 찾아낼 수 있도록 설계되었습니다. 이 과정에서 해시 함수와 배열(bucket 또는 slot이라고도 함)이 핵심적인 역할을 합니다.
2. 해시 함수의 역할
해시 테이블의 핵심은 해시 함수(Hash Function)입니다. 해시 함수는 임의의 길이의 key를 입력받아, 고정된 길이의 해시 값(hash value)을 생성합니다. 해시 값은 해시 테이블 내에서 데이터가 저장될 위치(index)를 결정하는 데 사용됩니다.
1) 해시 함수의 작동 원리
해시 함수는 입력된 키를 특정 알고리즘을 거쳐 정수 형태의 해시 값으로 변환합니다. 이 해시 값은 해시 테이블의 크기(배열의 크기)를 넘지 않는 범위 내에서 결정되어야 합니다. 가장 간단한 해시 함수는 키 값을 해시 테이블의 크기로 나눈 나머지(modulo 연산)를 사용하는 것입니다.
예를 들어, 해시 테이블의 크기가 10이고, 키가 "apple"인 경우 해시 함수는 다음과 같이 동작할 수 있습니다.
- "apple"을 숫자 값으로 변환 (예: 각 문자의 ASCII 값의 합).
- 변환된 숫자 값을 10으로 나눈 나머지 계산.
- 나머지가 해시 값, 즉 저장 위치의 인덱스가 됨.
2) 좋은 해시 함수의 특징
좋은 해시 함수는 다음과 같은 특징을 가져야 합니다.
- 빠른 계산: 해시 함수는 데이터를 삽입, 탐색, 삭제하는 과정에서 빈번하게 호출되므로, 계산 속도가 빨라야 합니다.
- 균등한 분포: 서로 다른 키에 대해 해시 값을 균등하게 생성해야 합니다. 즉, 해시 값들이 해시 테이블 전체에 고르게 분포되도록 해야 합니다. 이는
충돌(collision)을 줄이는 데 매우 중요합니다. - 결정성: 동일한 키에 대해서는 항상 동일한 해시 값을 반환해야 합니다.

3. 충돌 해결 방법
해시 함수가 서로 다른 키에 대해 동일한 해시 값을 생성하는 경우, 충돌(Collision)이 발생합니다. 충돌은 해시 테이블의 성능을 저하시키는 주요 원인이므로, 효율적인 충돌 해결 방법이 필요합니다.
1) 개방 주소법 (Open Addressing)
개방 주소법은 충돌이 발생하면 다른 빈 슬롯(slot)을 찾아 데이터를 저장하는 방식입니다.
- 선형 탐사(Linear Probing): 충돌 발생 시, 다음 슬롯을 차례대로 탐사하여 빈 슬롯을 찾습니다. 간단하지만, 데이터가 밀집될 경우 탐사 시간이 길어지는 단점이 있습니다.
- 제곱 탐사(Quadratic Probing): 충돌 발생 시, 제곱 간격으로 다음 슬롯을 탐사합니다. 선형 탐사보다 덜 밀집되지만, 이차 클러스터링(clustering)이 발생할 수 있습니다.
- 이중 해싱(Double Hashing): 두 번째 해시 함수를 사용하여 탐사 간격을 결정합니다.

2) 분리 연결법 (Separate Chaining)
분리 연결법은 각 슬롯에 연결 리스트(linked list)를 사용하여 충돌을 해결합니다. 충돌이 발생하면, 해당 슬롯에 연결된 리스트에 데이터를 추가합니다.
- 장점: 간단하고, 해시 테이블의 크기보다 더 많은 데이터를 저장할 수 있습니다.
- 단점: 연결 리스트의 탐색 시간이 필요하며, 리스트가 길어질수록 성능이 저하됩니다.

4. 해시 테이블의 활용
해시 테이블은 뛰어난 성능 덕분에 다양한 분야에서 널리 활용됩니다.
1) 데이터베이스 인덱싱
데이터베이스 시스템에서, 해시 테이블은 데이터 검색 속도를 향상시키기 위해 인덱싱(indexing)에 사용됩니다. 인덱스를 통해 특정 조건을 만족하는 데이터를 빠르게 찾을 수 있습니다.
2) 캐싱(Caching)
자주 사용되는 데이터를 메모리에 저장하여 접근 속도를 높이는 캐싱 기술에 해시 테이블이 활용됩니다. 예를 들어, 웹 브라우저의 캐시는 웹 페이지를 빠르게 로드하기 위해 해시 테이블을 사용하여 저장된 데이터를 관리합니다.
3) 컴파일러
컴파일러는 소스 코드를 분석하고, 심볼 테이블(symbol table)을 사용하여 변수, 함수 등의 정보를 관리합니다. 해시 테이블은 심볼 테이블을 구현하는 데 효과적인 자료구조입니다.
4) 기타 활용 분야
- 연관 배열(Associative Arrays): Python의 딕셔너리(dictionary), Java의 HashMap 등
- 중복 확인: 데이터의 중복을 빠르게 확인해야 하는 경우
- DNS (Domain Name System): 도메인 이름을 IP 주소로 매핑
5. 해시 테이블 구현 시 고려 사항
해시 테이블을 구현할 때, 몇 가지 사항을 고려해야 합니다.
1) 해시 테이블의 크기
해시 테이블의 크기는 저장할 데이터의 양과 예상되는 충돌 빈도에 따라 결정해야 합니다. 너무 작은 크기는 충돌을 증가시키고, 너무 큰 크기는 메모리 낭비를 초래할 수 있습니다. 일반적으로, load factor (저장된 데이터 개수 / 해시 테이블 크기)를 적절하게 유지하는 것이 중요합니다.
2) 해시 함수 선택
사용하는 데이터의 특성에 맞는 해시 함수를 선택해야 합니다. 좋은 해시 함수는 데이터가 해시 테이블에 균등하게 분산되도록 합니다. 키 값의 분포, 데이터 유형 등을 고려하여 해시 함수를 선택해야 합니다.
3) 충돌 해결 전략 선택
개방 주소법과 분리 연결법 중 적절한 충돌 해결 전략을 선택해야 합니다. 개방 주소법은 메모리 사용량이 적지만, 데이터가 밀집되면 성능이 저하될 수 있습니다. 분리 연결법은 메모리 사용량이 더 많지만, 데이터 밀집 문제에서 자유롭습니다.
6. 해시 테이블의 시간 복잡도
해시 테이블의 성능은 평균적으로 매우 뛰어납니다.
-
탐색, 삽입, 삭제: O(1) (평균 시간 복잡도)
- 충돌이 발생하지 않거나, 충돌 해결 방법이 효율적인 경우.
- 최악의 경우: O(n)
- 모든 키가 동일한 해시 값을 생성하여, 모든 데이터가 동일한 슬롯에 저장되는 경우 (분리 연결법에서는 연결 리스트를 탐색해야 하므로 O(n)이 됨). 개방 주소법에서는 테이블 전체를 탐색해야 할 수도 있습니다.
7. 결론
해시 테이블은 자료구조 중에서도 특히 빠른 성능을 제공하며, 다양한 분야에서 널리 활용되는 중요한 자료구조입니다. 해시 함수, 충돌 해결 방법, 구현 시 고려 사항 등을 이해하는 것은 효율적인 프로그램을 작성하는 데 필수적입니다. 데이터의 양, 데이터의 특성, 그리고 사용 목적에 따라 적절한 해시 테이블 구현 방법을 선택하여 활용하는 것이 중요합니다.
8. 추가적인 설명
1) 해시 테이블의 동적 크기 조절 (Dynamic Resizing)
해시 테이블에 저장되는 데이터의 수가 증가함에 따라, 충돌이 증가하고 성능이 저하될 수 있습니다. 이러한 문제를 해결하기 위해, 해시 테이블은 데이터의 양에 따라 크기를 동적으로 조절할 수 있습니다.
- Load Factor: 해시 테이블의
load factor가 임계치를 넘으면(예: 0.75), 해시 테이블의 크기를 증가시킵니다. - Rehashing: 해시 테이블의 크기가 변경되면, 기존의 모든 데이터를 새로운 해시 테이블에 다시 삽입하는 과정(rehash)이 필요합니다.
동적 크기 조절은 해시 테이블의 성능을 유지하는 데 중요한 역할을 합니다.
2) 실용적인 해시 함수 예시
다양한 해시 함수가 존재하며, 사용하는 데이터 타입과 상황에 따라 적합한 해시 함수를 선택해야 합니다. 다음은 몇 가지 실용적인 해시 함수의 예시입니다.
- MurmurHash: 빠르고 효율적인 해시 함수로, 다양한 키 타입에 적합합니다.
- SHA-256: 암호화 해시 함수로, 보안이 중요한 경우에 사용됩니다.
3) 해시 테이블의 공간 복잡도
해시 테이블의 공간 복잡도는 데이터의 개수(n)에 비례합니다. 즉, O(n)입니다. 분리 연결법을 사용하는 경우, 각 슬롯에 연결 리스트가 저장되므로 추가적인 공간이 필요합니다. 개방 주소법을 사용하는 경우, 해시 테이블의 크기에 따라 공간 사용량이 달라집니다.
4) Java HashMap 예시
Java의 HashMap은 해시 테이블의 대표적인 구현체입니다. HashMap은 key-value 쌍을 저장하며, 빠른 탐색, 삽입, 삭제를 지원합니다.
import java.util.HashMap;
public class HashMapExample {
public static void main(String[] args) {
// HashMap 생성
HashMap<String, Integer> map = new HashMap<>();
// 데이터 삽입
map.put("apple", 1);
map.put("banana", 2);
map.put("cherry", 3);
// 데이터 탐색
int value = map.get("banana"); // value = 2
// 데이터 삭제
map.remove("cherry");
// key-value 쌍 개수
int size <mark class="highlight"><strong><u> map.size(); // size </u></strong></mark> 2
}
}
Java의 HashMap은 분리 연결법을 사용하여 충돌을 해결하며, 동적 크기 조절 기능을 제공합니다.
Comments (0)
No comments yet. Be the first to comment!
Please to write a comment.