C 언어로 된 해시 테이블에서 작업 중이며 문자열에 대한 해시 함수를 테스트하고 있습니다.
내가 시도한 첫 번째 기능은 ascii 코드를 추가하고 모듈로 (% 100)를 사용하는 것이지만 첫 번째 데이터 테스트에서 좋지 않은 결과를 얻었습니다 : 130 단어에 대해 40 개의 충돌.
최종 입력 데이터에는 8 000 단어가 포함됩니다 (파일에 사전 저장 됨). 해시 테이블은 int table [10000]로 선언되며 txt 파일에서 단어의 위치를 포함합니다.
첫 번째 질문은 문자열 해싱에 가장 적합한 알고리즘은 무엇입니까? 해시 테이블의 크기를 결정하는 방법은 무엇입니까?
미리 감사드립니다!
🙂
답변
나는 djb2
Dan Bernstein으로 좋은 결과를 얻었습니다 .
unsigned long
hash(unsigned char *str)
{
unsigned long hash = 5381;
int c;
while (c = *str++)
hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
return hash;
}
답변
첫째, 일반적으로 해시 테이블에 암호화 해시를 사용하고 싶지 않습니다 . 암호화 표준에 따라 매우 빠른 알고리즘은 해시 테이블 표준에 의해 여전히 매우 느립니다.
둘째, 입력의 모든 비트가 결과에 영향을 미칠 수 있는지 확인하려고합니다. 이를 수행하는 한 가지 쉬운 방법은 현재 결과를 일부 비트 수만큼 회전시킨 다음 현재 해시 코드를 현재 바이트와 XOR하는 것입니다. 문자열 끝에 도달 할 때까지 반복합니다. 일반적으로 회전이 바이트 크기의 배수가되는 것을 원하지 않습니다 .
예를 들어 8 비트 바이트의 일반적인 경우를 가정하면 5 비트 씩 회전 할 수 있습니다.
int hash(char const *input) {
int result = 0x55555555;
while (*input) {
result ^= *input++;
result = rol(result, 5);
}
}
편집 : 또한 10000 개의 슬롯이 해시 테이블 크기에 적합한 선택은 거의 없습니다. 일반적으로 두 가지 중 하나를 원합니다. 크기로 소수를 원하거나 (일부 유형의 해시 해상도로 정확성을 보장하는 데 필요함) 그렇지 않으면 2의 거듭 제곱을 원합니다 (따라서 값을 올바른 범위로 줄이는 것은 간단한 방법으로 수행 할 수 있습니다. 비트 마스크).
답변
Wikipedia는 Jenkins One At A Time Hash라는 멋진 문자열 해시 함수를 보여줍니다 . 또한이 해시의 개선 된 버전을 인용합니다.
uint32_t jenkins_one_at_a_time_hash(char *key, size_t len)
{
uint32_t hash, i;
for(hash = i = 0; i < len; ++i)
{
hash += key[i];
hash += (hash << 10);
hash ^= (hash >> 6);
}
hash += (hash << 3);
hash ^= (hash >> 11);
hash += (hash << 15);
return hash;
}
답변
C 표준 라이브러리 hcreate / hdestroy / hsearch에서 APR 및 glib 의 C에 대한 기존 해시 테이블 구현이 많이 있습니다.이 구현 은 미리 빌드 된 해시 함수도 제공합니다. 고유 한 해시 테이블이나 해시 함수를 개발하는 것보다 사용하는 것이 좋습니다. 일반적인 사용 사례에 맞게 크게 최적화되었습니다.
그러나 데이터 세트가 정적 인 경우 가장 좋은 해결책은 완벽한 해시 를 사용하는 것입니다 . gperf 는 주어진 데이터 세트에 대해 완벽한 해시를 생성합니다.
답변
djb2에는 이 466k 영어 사전에 대해 317 개의 충돌 이있는 반면 MurmurHash에는 64 비트 해시에 대해 없음, 32 비트 해시에 대해 21 개 (466k 임의 32 비트 해시에 대해 약 25 개가 예상 됨)가 있습니다. 내 권장 사항은 가능한 경우 MurmurHash 를 사용하는 것입니다. 한 번에 몇 바이트를 차지하기 때문에 매우 빠릅니다. 그러나 프로젝트에 복사하여 붙여 넣기 위해 간단하고 짧은 해시 함수가 필요한 경우 한 번에 1 바이트 버전을 사용하는 것이 좋습니다.
uint32_t inline MurmurOAAT32 ( const char * key)
{
uint32_t h(3323198485ul);
for (;*key;++key) {
h ^= *key;
h *= 0x5bd1e995;
h ^= h >> 15;
}
return h;
}
uint64_t inline MurmurOAAT64 ( const char * key)
{
uint64_t h(525201411107845655ull);
for (;*key;++key) {
h ^= *key;
h *= 0x5bd1e9955bd1e995;
h ^= h >> 47;
}
return h;
}
해시 테이블의 최적 크기는 간단히 말해서 메모리에 맞추면서 가능한 한 큰 것입니다. 일반적으로 사용 가능한 메모리 양을 모르거나 조회하고 싶지 않고 변경 될 수도 있기 때문에 최적의 해시 테이블 크기는 테이블에 저장 될 예상 요소 수의 약 2 배입니다. 그보다 훨씬 더 많이 할당하면 해시 테이블이 더 빨라지지만 수익이 급격히 감소하여 해시 테이블이 그보다 작아지면 기하 급수적으로 느려집니다. 이는 해시 테이블에 대한 공간 및 시간 복잡성 사이에 비선형 균형 이 있기 때문이며 , 최적의 부하 계수는 2-sqrt (2) = 0.58 …이 분명합니다.
답변
첫째, 0..99로 해시 된 130 개의 단어에 대해 40 개의 충돌이 나쁘나요? 특별한 조치를 취하지 않으면 완벽한 해싱을 기대할 수 없습니다. 일반적인 해시 함수는 대부분의 경우 랜덤 생성기보다 충돌이 적지 않습니다.
평판이 좋은 해시 함수는 MurmurHash3 입니다.
마지막으로, 해시 테이블의 크기와 관련하여, 특히 버킷이 확장 가능한지 또는 단일 슬롯인지 여부를 염두에두고있는 해시 테이블의 종류에 따라 다릅니다. 버킷을 확장 할 수있는 경우 다시 선택할 수 있습니다. 보유한 메모리 / 속도 제약에 대한 평균 버킷 길이를 선택합니다.
답변
비록 djb2
로, cnicutar에 의해 유래에 관한 더 나은 거의 확실하다, 나는 보여주는 그것의 가치가 생각 K & R이 너무 해시 :
1) 분명히 끔찍한 해시 알고리즘, K & R 1 판 ( 출처 )
unsigned long hash(unsigned char *str)
{
unsigned int hash = 0;
int c;
while (c = *str++)
hash += c;
return hash;
}
2) 아마도 K & R 버전 2에 제시된 꽤 괜찮은 해시 알고리즘 일 것이다 (책의 144 페이지에서 내가 확인 함); 주의 : % HASHSIZE
해시 알고리즘 외부에서 배열 길이에 맞게 모듈러스 크기 조정을 수행 할 계획이라면 return 문에서 제거해야 합니다. 또한 unsigned long
단순 unsigned
(int) 대신 return 및 “hashval”유형을 사용 하는 것이 좋습니다 .
unsigned hash(char *s)
{
unsigned hashval;
for (hashval = 0; *s != '\0'; s++)
hashval = *s + 31*hashval;
return hashval % HASHSIZE;
}
그것은 고려 문자열의 문자를 고려하지 않기 때문에 1 판 해시 그렇게 끔찍한 일 이유는 그 두 알고리즘에서 분명 있음을 참고 하기 위해 , 그래서 hash("ab")
때문에 같은 값을 반환합니다 hash("ba")
. 이다 하지 (훨씬 더!) 그 문자열을 두 개의 서로 다른 값을 반환 할 것이다, 그러나, 그래서 제 2 판 해시.
unordered_map
(해시 테이블 템플릿) 및 unordered_set
(해시 세트 템플릿)에 사용되는 GCC C ++ 11 해싱 함수 는 다음과 같습니다.
- 이것은 GCC 가 Austin Appleby ( http://murmurhash.googlepages.com/ ) 의 “MurmurHashUnaligned2″구현을 사용함을 나타내는 GCC C ++ 11 해시 함수 가 무엇인지 에 대한 질문에 대한 부분적인 답변 입니다.
- “gcc / libstdc ++-v3 / libsupc ++ / hash_bytes.cc”파일 ( https://github.com/gcc-mirror/gcc/blob/master/libstdc++-v3/libsupc++/hash_bytes.cc )에서 구현. 다음은 “32 비트 size_t”반환 값에 대한 예입니다 (예 : 2017 년 8 월 11 일 가져옴).
암호:
// Implementation of Murmur hash for 32-bit size_t.
size_t _Hash_bytes(const void* ptr, size_t len, size_t seed)
{
const size_t m = 0x5bd1e995;
size_t hash = seed ^ len;
const char* buf = static_cast<const char*>(ptr);
// Mix 4 bytes at a time into the hash.
while (len >= 4)
{
size_t k = unaligned_load(buf);
k *= m;
k ^= k >> 24;
k *= m;
hash *= m;
hash ^= k;
buf += 4;
len -= 4;
}
// Handle the last few bytes of the input array.
switch (len)
{
case 3:
hash ^= static_cast<unsigned char>(buf[2]) << 16;
[[gnu::fallthrough]];
case 2:
hash ^= static_cast<unsigned char>(buf[1]) << 8;
[[gnu::fallthrough]];
case 1:
hash ^= static_cast<unsigned char>(buf[0]);
hash *= m;
};
// Do a few final mixes of the hash.
hash ^= hash >> 13;
hash *= m;
hash ^= hash >> 15;
return hash;
}
