1 분 소요

해시

정의와 성질

  • 해시에서는 insert, erase, find, update 모두 O(1)
  • 해시 함수: 임의의 길이의 데이터를 고정된 길이의 데이터로 대응시키는 함수

    충돌

  • 서로 다른 키가 같은 해시 값을 가질 경우 충돌이 일어남
  • 충돌이 발생했을 때 회피 방안은 Chaining과 Open Addressing

    Chaining

  • 각 인덱스마다 연결 리스트를 둬서 충돌 회피를 하는 방법
  • STL에 있는 해시 자료구조는 Chaining을 사용
  • 이상적인 상황에서는 O(1)이지만 충돌이 빈번할수록 성능이 저하되고 모든 키의 해시 값이 같으면 O(N)이 됨
  • 각 키의 해시 값이 최대한 균등하게 퍼져야 성능이 좋아지기 때문에 좋은 해시 함수를 정해야 함

    Open Addressing

  • 각 인덱스에 바로 (키,값)쌍을 씀
  • 해당 칸에 값이 있는 경우 그 다음 칸에 저장
  • 삭제를 할 때 그냥 값을 날려서 빈 칸을 만들지 않고 해당 칸에 쓰레기 값을 두던가 별도의 배열 두어서 해당 칸이 빈 칸이 아니라 원래 값이 있었지만 제거된 상태임을 명시함

    Linear Probing

  • 충돌 발생 시 오른쪽으로 1칸씩 이동하는 방식
  • 장점: Cache hit rate가 높음
  • 단점: Clustering이 생겨 성능에 영향을 줄 수 있음

    Quadratic Probing

  • 충돌 발생 시 오른쪽으로 1,3,5,…칸씩 이동하는 방식
  • 처음 위치 기준으로 1,4,9…칸씩 이동하기 때문에(제곱방식)
  • 장점: Cache hit rate가 나쁘지 않고 Clustering을 어느 정도 회피할 수 있음
  • 단점: 해시 값이 같을 경우 여전히 Clustering이 발생함

    Double Hashing

  • 충돌 발생 시 이동할 칸의 수를 새로운 해시 함수로 계산하는 방식
  • 장점: Clustering을 효과적으로 회피할 수 있음
  • 단점: Cache hit rate가 낮음

    STL

  • unordered_set
    #include<iostream>
    #include<unordered_set>
    using namespace std;
    int main(void){
      unordered_set<int> s;
      s.insert(-10);
      s.insert(100);
      s.insert(15);
      //{-10,100,15}
      s.insert(-10); // 중복을 허용하지 않기 때문에 변화 없음
      //{-10,100,15}
      cout<<s.erase(100)<<'\n';//출력:1 {-10,15}
      cout<<s.erase(20)<<'\n';//출력:0 {-10,15}
      if(s.find(15)!=s.end()){
          cout<<"yes\n";
      }
      cout<<s.size()<<'\n'; // 2
      cout<<s.count(50)<<'\n'; //0
      for(auto e:s){//해시의 특성상 원소들이 크기 순서 혹은 삽입한 순서로 위치해 있지 않음
          cout<<e<<' ';
      }
      cout<<'\n';
      return 0;
    }
    
  • unordered_multiset
    #include<iostream>
    #include<unordered_set>
    using namespace std;
    int main(void){
      unordered_multiset<int> ms;
      ms.insert(-10);
      ms.insert(100);
      ms.insert(15);// {-10,100,15}
      ms.insert(-10);
      ms.insert(15);// {-10,-10,100,15,15}
      cout<<ms.size()<<'\n';// 5
      for(auto e : ms){
          cout<<e<<' ';
      }
      cout<<'\n';
      cout<<ms.erase(15)<<'\n'; //출력: 2 {-10,-10,100} 하나만 지워지지 않고 모든 15가 지워짐
      ms.erase(ms.find(-10)); //{-10,100}
      ms.insert(100); //{-10,100,100}
      cout<<ms.count(100)<<'\n';// 2
      return 0;
    }
    
  • unordered_map
    #include<iostream>
    #include<unordered_map>
    using namespace std;
    int main(void){
      unordered_map<string,int> m;
      m["hi"]=123;
      m["bird"]=1000;
      m["gogo"]=165; // {"hi",123},{"bird",1000},{"gogo",165}
      cout<<m.size()<<'\n'; //3
      m["hi"]=-7; // {"hi",-7},{"bird",1000},{"gogo",165} 중복키를 허용하지 않음
      if(m.find("hi")!=m.end()){
          cout<<"yes\n";
      }
      else{
          cout<<"no\n";
      }
      m.erase("bird"); //{"hi",-7},{"gogo",165}
      for(auto e:m){ //{"hi",-7},{"gogo",165}
          cout<<e.first<<' '<<e.second<<'\n';
      }
      return 0;
    }