본문 바로가기
카테고리 없음

[바킹독의 실전 알고리즘] 0x15 해시

by Unagi_zoso 2022. 7. 19.

insert, erase, find, update 모두 O(1)

 

해시 함수는 임의 길이의 데이터를 고정된 길이의 데이터로 대응시키는 함수

 

해시에서 가장 중요한 사항은 충돌이고 이 충돌을 잘 처리하는게 해시의 성능에 가장 큰 영향을 준다.

서로 다른 키가 같은 해시값을 가지면  충돌 발생

해시의 의의에는 함수의 입력으로 주어지는 정의역이 너무커서 인덱스로 사용할 수 없으니 

그 크기를 줄이는 것. 충돌은 어쩔 수 없다. 

허나 회피하는 방법이 연구되었다.

 

첫 번째가 Chaining

 

Chaining에서는 각 인덱스마다 연결 리스트를 하나씩 둠

STL 해시 자료구조는 Chaining 사용

최악 경우 find  , update O(N)

균등하게 퍼지도록 좋은 해시함수를 찾는 것이 중요

 

두 번째 Openaddressing

 

해당 칸이 이미 있는 경우 옆 칸에 저장한다.

찾을 시 해당 장소에 워나는 값이 없다면 옆 칸을 다 찾는다.

찾는 값이 없을 시 빈칸에 가게되고 없음을 파악

삭제 시 그냥 지워버리면 find 시 존재하는 값을 찾지 못하게 될 수 있음

그러니 쓰레기값을 대신 넣어버렷. 쓰레기값에 insert 가능

 

 

Linear Probing 탐사병

 

충돌 발생 시 오른쪽으로 1칸씩 이동하는 방식

장점 - Cache hit rate가 높다

단점 Clustering이 생겨 성능에 영향을 줄 수 있다.

 

Quadratic Probing 제곱하며 탐사

충돌 발생 시 오른쪽으로 1, 3, 5, ... 이동하는 방식

장점 Cache hit rate가나쁘지 않다, Clustering을 어느정도 회피할 수 있다.

단점 해시 값이 같을 경우 여전히 Clustering이 발생

 

Double Hashing = 충돌 발생 시 이동할 칸의 수를 새로운 해시 함수로 계산하는 방식

장점 Clustering을 효과적으로 회피할 수 있다.

 

 

STL

 

unordered_set<int> s;
s.insert(-10);
s.erase(100); 지웠으면 1 실패 0
if (s.find(15) != s.end()) 
s.size()
s.count(50)
for (auto e : s)

반복자 존재

 

unordered_multiset 중복허용 erase 조심해야해 전부 다 지워짐

unordered_multiset<int> ms
ms.insert(-1);
ms.size();
for (auto e : ms)
ms.erase(15);
ms.erase(ms.find(-10)); find 반복자 반환 하나만 지움
ms.count(100);

 

unordered_map<string, int> m;
m["hi"] = 123;
m]"bkd"]  1000;
m["gogo"] = 165;
m.size()
if (m.find("hi") != mend())
m.erase("bkd");
for (auto e : m)

 

 

구현

 

해시 테이블 크기, 해시 함수

Load factor = 원소의 개수 / 테이블의 크기

LF 클 시 충돌 자주 발생하지만 cache hit rate 높고

낮을 시 충돌 덜 발생, chr 낮음

보통 LF를 1이하로 맞춤 이 경우 Open addressing에서는

빈칸을 찾는데에 시간을 소비해 load factor를 0.75 정도로 준다.

 

정수에 대한 해시 함수
const int M = 1000003;
int my_hash(int x) {
	return (M + x % M) % M;
}

문자열에 대한 해시 함수
const int M = 1000003;
int hash(string* s) {
	int h = 0;
    for (auto x : s) h += x;
   	return h % M;
}
// 결과가 집중되는 성향 있어 충돌 빈번

문자열을 진법처럼 처리 하면 그나마 낫다
(롤링 해시)
const int M = 1000003;
const int a = 1000;
int my_hash(string& s) }
	int h = 0;
    for (auto x : s) 
    	h = (h * a + x) % M;
    return h;
}
const int M = 1000003;
const int a = 1000;
const int MX = 500005;
int head[M];
int pre[MX];
int nxt[MX];
string key[MX];
int  val[MX];
int unused = 0;

int my_hash(string& s) {
    int h = 0;
    for (auto x : s) 
        h = (h * a + x) % M;
    return h;
}

int find(string k) {
    int h = my_hash(k);
    int idx = head[h];
    while (idx != -1) {
        if (key[idx] == k) return idx;
        idx = nxt[idx];
    }
    return -1;
}

void insert(string k, int v) {
    int idx = find(k);
    if (idx != -1) {
        val[idx] = v;
        return;
    }
    int h = my_hash(k);
    key[unused] = k;
    val[unused] = v;
    if (head[h] != -1) {
        nxt[unused] = head[h];
        pre[head[h]] =unused;
    }
    head[h] = unused;
    unused++ ;
}

void erase(string k) {
    int idx = find(k);
    if (idx == -1) return;
    if (pre[idx] != -1) nxt[pre[idx]] = nxt[idx];
    if (nxt[idx] != 01) pre[nxt[idx]] = pre[idx];
    int h = my_hash(k);
    if (head[j] == idx) head[h] = nxt[idx];
}

댓글