3 분 소요

해시 구현

구현

  • 해시 테이블 크기, 해시 함수를 정해야 함
  • Load factor: 원소의 개수/테이블의 크기
  • Load factor를 작게 유지하면 충돌이 덜 생겨서 각 연산이 거의 O(1)에 동작하지만 cache hit rate가 줄어들고 메모리도 많이 차지하는 단점이 있음
  • Chaining에서는 load factor를 1이하가 되게 하고 Open addressing에서는 0.75이하로 둠
  • 테이블의 크기가 소수이면 좋음
  • 정수에 대한 해시 함수
    // 정수에 대한 해시 함수
    const int M=1000003;
    int my_hash(int x){
      return (M+x%M)%M; //음수에 대해서도 가능
    }
    
  • 문자열에 대한 해시 함수
    // 문자열에 대한 해시 함수(비효율)
    const int M=1000003;
    int my_hash(string& s){
      int h=0;
      for(auto x: s){
          h+=x;   //간단하지만 치명적(충돌이 빈번하게 발생해서 좋지 않은 해시 함수)
      }
      return h%M;
    }
    // 문자열에 대한 해시 함수(효율)
    // rolling hash라는 방법 사용
    // ex) my_hash("abc")=('a'*1000^2 + 'b'*1000^1 + 'c'*1000^0)%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%M;
    }
    

    구현(Chaining)

    #include<iostream>
    #include<cassert>
    using namespace std;
    const int M = 1000003; //테이블의 크기를 의미
    const int a = 1000;
    const int MX = 500005; //최대 삽입 횟수
    int head[M]; //각 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;
    }
    //key[idx] == k인 idx를 반환, 만약 k가 존재하지 않을 경우 -1을 반환
    //키에 대응되는 값을 반환하는게 아니라 인데그를 반환함에 주의
    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]!=-1){
          pre[nxt[idx]]=pre[idx];
      }
      int h=my_hash(k);
      if(head[h]==idx){
          head[h]=nxt[idx];
      }
    }
    void test(){
      insert("orange", 724); // ("orange", 724)
      insert("melon", 20); // ("orange", 724), ("melon", 20)
      assert(val[find("melon")] == 20);
      insert("banana", 52); // ("orange", 724), ("melon", 20), ("banana", 52)
      insert("cherry", 27); // ("orange", 724), ("melon", 20), ("banana", 52), ("cherry", 27)
      insert("orange", 100); // ("orange", 100), ("melon", 20), ("banana", 52), ("cherry", 27)
      assert(val[find("banana")] == 52);
      assert(val[find("orange")] == 100);
      erase("wrong_fruit"); // ("orange", 100), ("melon", 20), ("banana", 52), ("cherry", 27)
      erase("orange"); // ("melon", 20), ("banana", 52), ("cherry", 27)
      assert(find("orange") == -1);
      erase("orange"); // ("melon", 20), ("banana", 52), ("cherry", 27)
      insert("orange", 15); // ("melon", 20), ("banana", 52), ("cherry", 27), ("orange", 15)
      assert(val[find("orange")] == 15);
      insert("apple", 36); // ("melon", 20), ("banana", 52), ("cherry", 27), ("orange", 15), ("apple", 36)
      insert("lemon", 6); // ("melon", 20), ("banana", 52), ("cherry", 27), ("orange", 15), ("apple", 36), ("lemon", 6)
      insert("orange", 701);  // ("melon", 20), ("banana", 52), ("cherry", 27), ("orange", 701), ("apple", 36), ("lemon", 6)
      assert(val[find("cherry")] == 27);
      erase("xxxxxxx");
      assert(find("xxxxxxx") == -1);
      assert(val[find("apple")] == 36);
      assert(val[find("melon")] == 20);
      assert(val[find("banana")] == 52);
      assert(val[find("cherry")] == 27);
      assert(val[find("orange")] == 701);
      assert(val[find("lemon")] == 6);  
      cout << "good!\n";  
    }
    int main(void){
      fill(head,head+M,-1);
      fill(pre,pre+MX,-1);
      fill(nxt,nxt+MX,-1);
      test();
      return 0;
    }
    

    Open Addressing

    #include<iostream>
    #include<cassert>
    using namespace std;
    const int M = 1000003; //테이블의 크기를 의미
    const int a = 1000;
    const int EMPTY = -1; // 비어있음
    const int OCCUPY = 0; // 칸에 값이 있음
    const int DUMMY = 1; // 칸에 값이 있었다가 삭제됨
    int status[M]; // EMPTY / OCCUPY / DUMMY
    string key[M];
    int val[M];
    int my_hash(string& s){
      int h=0;
      for(auto x:s){
          h=(h*a+x)%M;
      }
      return h;
    }
    //key[idx] == k인 idx를 반환, 만약 k가 존재하지 않을 경우 -1을 반환
    //키에 대응되는 값을 반환하는게 아니라 인데그를 반환함에 주의
    int find(string k){
      int idx=my_hash(k);
      while(status[idx]!=EMPTY){
          if(status[idx]==OCCUPY && key[idx]==k){
              return idx;
          }
          idx=(idx+1) %M;
      }
      return -1;
    }
    void insert(string k,int v){
      int idx=find(k);
      if(idx!=-1){
          val[idx]=v;
          return;
      }
      idx=my_hash(k);
      while(status[idx]==OCCUPY){
          idx=(idx+1)%M;
      }
      key[idx]=k;
      val[idx]=v;
      status[idx]=OCCUPY;
    }
    void erase(string k){
      int idx=find(k);
      if(idx!=-1){
          status[idx]=DUMMY;
      }
    }
    void test(){
      insert("orange", 724); // ("orange", 724)
      insert("melon", 20); // ("orange", 724), ("melon", 20)
      assert(val[find("melon")] == 20);
      insert("banana", 52); // ("orange", 724), ("melon", 20), ("banana", 52)
      insert("cherry", 27); // ("orange", 724), ("melon", 20), ("banana", 52), ("cherry", 27)
      insert("orange", 100); // ("orange", 100), ("melon", 20), ("banana", 52), ("cherry", 27)
      assert(val[find("banana")] == 52);
      assert(val[find("orange")] == 100);
      erase("wrong_fruit"); // ("orange", 100), ("melon", 20), ("banana", 52), ("cherry", 27)
      erase("orange"); // ("melon", 20), ("banana", 52), ("cherry", 27)
      assert(find("orange") == -1);
      erase("orange"); // ("melon", 20), ("banana", 52), ("cherry", 27)
      insert("orange", 15); // ("melon", 20), ("banana", 52), ("cherry", 27), ("orange", 15)
      assert(val[find("orange")] == 15);
      insert("apple", 36); // ("melon", 20), ("banana", 52), ("cherry", 27), ("orange", 15), ("apple", 36)
      insert("lemon", 6); // ("melon", 20), ("banana", 52), ("cherry", 27), ("orange", 15), ("apple", 36), ("lemon", 6)
      insert("orange", 701);  // ("melon", 20), ("banana", 52), ("cherry", 27), ("orange", 701), ("apple", 36), ("lemon", 6)
      assert(val[find("cherry")] == 27);
      erase("xxxxxxx");
      assert(find("xxxxxxx") == -1);
      assert(val[find("apple")] == 36);
      assert(val[find("melon")] == 20);
      assert(val[find("banana")] == 52);
      assert(val[find("cherry")] == 27);
      assert(val[find("orange")] == 701);
      assert(val[find("lemon")] == 6);  
      cout << "good!\n";  
    }
    int main(void){
      fill(status,status+M,EMPTY);
      test();
      return 0;
    }