반응형
1. 다항식 롤링 해시란?
다항식 롤링 해시는 문자열의 각 문자를 일정한 베이스(예: 131, 257 등) 를 이용해 하나의 정수로 변환하는 방식입니다.
예를 들어, 문자열 s에 대해 해시값을 아래와 같이 계산할 수 있습니다.
하지만 실제 구현에서는 오버플로우와 연산 효율을 고려하여 보통 누적 방식으로 계산합니다.
코드로 살펴보기
const int TABLE_SIZE = 100003; // 큰 소수를 사용하는 것이 좋습니다.
int getHash(const string &s) {
unsigned long long hash_val = 0;
for (char c : s) {
hash_val = hash_val * 131 + c; // 131은 베이스로 자주 사용되는 소수입니다.
}
return hash_val % TABLE_SIZE; // 해시 테이블의 인덱스 범위 내로 제한
}
- 설명:
- hash_val을 0에서 시작하여 문자열의 각 문자를 순회하면서 hash_val * 131 + c로 업데이트합니다.
- 최종적으로 TABLE_SIZE로 나눈 나머지를 반환하여, 해시 테이블 내에서 사용할 인덱스를 결정합니다.
2. 해시 테이블 구현 및 충돌 처리
해시 테이블은 해시 함수를 이용하여 데이터를 빠르게 저장하고 찾는 자료구조입니다.
문자열의 해시값을 인덱스로 사용하여 데이터를 저장하는데, 서로 다른 문자열이 같은 인덱스를 갖는 경우 충돌(Collision) 이 발생할 수 있습니다.
체이닝(Chaining) 방식으로 충돌 해결하기
아래는 체이닝 기법을 사용하여 문자열 집합의 교집합 문제를 해결하는 예제입니다.
첫 번째 집합의 문자열을 해시 테이블에 저장하고, 두 번째 집합의 문자열을 검색하여 교집합 개수를 계산합니다.
#include <iostream>
#include <vector>
#include <string>
using namespace std;
const int TABLE_SIZE = 100003; // 해시 테이블 크기
// 다항식 롤링 해시 함수 (베이스: 131)
int getHash(const string &s) {
unsigned long long hash_val = 0;
for (char c : s) {
hash_val = hash_val * 131 + c;
}
return hash_val % TABLE_SIZE;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin >> T;
for (int t = 1; t <= T; t++) {
int N, M;
cin >> N >> M;
// 해시 테이블: 각 버킷은 문자열들을 저장하는 벡터입니다.
vector<vector<string>> hashTable(TABLE_SIZE);
string str;
// 첫 번째 집합의 문자열들을 해시 테이블에 저장합니다.
for (int i = 0; i < N; i++) {
cin >> str;
int idx = getHash(str);
hashTable[idx].push_back(str);
}
int answer = 0;
// 두 번째 집합의 문자열들을 확인하며 교집합을 찾습니다.
for (int i = 0; i < M; i++) {
cin >> str;
int idx = getHash(str);
// 해당 버킷 내에서 문자열이 존재하는지 선형 탐색합니다.
for (const auto &s : hashTable[idx]) {
if (s == str) {
answer++;
break; // 집합 내 중복은 없으므로, 한 번 찾으면 바로 종료합니다.
}
}
}
cout << "#" << t << " " << answer << "\n";
}
return 0;
}
- 핵심 포인트:
- 해시 테이블 구조:
vector<vector<string>> 형태로 각 인덱스(버킷)에 여러 문자열을 저장할 수 있도록 하여, 충돌이 발생해도 체이닝 방식으로 처리합니다. - 문자열 저장 및 검색:
첫 번째 집합의 각 문자열을 해시 함수로 계산한 인덱스에 저장하고, 두 번째 집합의 문자열 역시 같은 방식으로 인덱스를 구해 해당 버킷에서 검색합니다.
- 해시 테이블 구조:
3. 해시 충돌 예측 및 예방
충돌 가능성
- 이론적으로:
모든 해시 함수는 무한한 입력을 유한한 범위로 매핑하기 때문에 충돌은 언제든지 발생할 수 있습니다. - 실제로:
- 베이스 값 (예: 131): 소수를 사용하면 해시 값이 균등하게 분포될 가능성이 높습니다.
- 테이블 크기 (TABLE_SIZE): 충분히 큰 소수(예: 100003)를 사용하면 평균적으로 각 버킷에 저장되는 항목 수가 매우 적어져 충돌 확률을 낮출 수 있습니다.
- 입력 데이터의 특성: 입력 데이터가 무작위라면 충돌이 적게 발생하지만, 특정 패턴이나 유사한 문자열들이 많다면 충돌 가능성이 올라갑니다.
예방 및 처리 방법
- 예방:
- 적절한 베이스와 모듈로 값을 선택합니다.
- 경우에 따라 이중 해싱(Double Hashing) 등의 기법을 사용할 수도 있습니다.
- 처리:
- 위 예제처럼 체이닝(Chaining) 방식을 사용하면 같은 인덱스에 여러 문자열을 저장하고, 선형 탐색을 통해 검색할 수 있습니다.
- 또는, 오픈 어드레싱(Open Addressing) 방식도 있지만, 체이닝이 구현하기 더 쉽습니다.
반응형
'알고리즘 > Tip' 카테고리의 다른 글
C++ 템플릿과 STL: 핵심 개념 정리 (0) | 2025.02.03 |
---|---|
동적프로그래밍(Dynamic Programming, DP) 의 두가지 주요 특성 (0) | 2024.11.06 |
동적계획법과 분할정복의 차이 ,그리고 동적계획법의 특징 (0) | 2024.09.08 |
C++ 문자열 자르기 총정리 (0) | 2024.06.19 |
백준 C++ eof 처리 방법 (0) | 2024.06.18 |