📗 Docs

해시 테이블(Hash Table)

파일과 미디어
date
Apr 14, 2023
slug
Algorithm-HashTable
author
status
Public
tags
Algorithm
Java
summary
HashTable 기본 개념 정리
type
Post
thumbnail
category
📗 Docs
updatedAt
Apr 14, 2023 05:31 PM

해시 테이블(Hash Table) = 해시 맵, 해시표

  • 키(Key), 값(Value)을 대응시켜 저장하는 데이터 구조
    • 키를 통해 해당 데이터에 빠르게 접근 가능
  • 해싱
    • 키를 특정 계산식에 넣어 나온 결과를 사용하여 값에 접근하는 과정
 

해시 테이블 기본 구조

  • 키 : 해시 테이블 접근을 위한 입력 값
  • 해시 함수(Hash function) : 키를 해시 값으로 매핑하는 연산
  • 해시 값(Hash valute): 해시 테이블의 인덱스
  • 해시 테이블 : 키-값을 연관시켜 저장하는 데이터 구조
 

해시 충돌

  • 해시 테이블의 같은 공간에 서로 다른 값을 저장하려는 경우
    • 서로 다른 키의 해시 함수를 통한 해시 값이 동일한 경우
  • 해시 충돌 해결 방법으로 크게 개방 주소법과 분리 연결법이 있음
 

개방 주소법(Open Address) - 해시 충돌 해결 방법(1)

  • 충돌 시 , 테이블에서 비어 있는 공간의 hash를 찾아 데이터를 저장
  • hash vlaue가 1:1관계 유지
  • 빙어 있는 공간 탐색 방법에 따라 분류
    • 선형 탐사법, 제곱 탐사법, 이중 해싱
1) 선형 탐사법(Linear Probing)
  • 빈 공간을 순차적으로 탐사하는 방법
    • 충돌 발생 지점 부터 이후의 빈 공간을 순서대로 탐사
  • 일차 군집화 문제 발생
    • 반복된 충돌 발생 시 해당 지점 주변에 데이터가 몰리는 경우 발생
https://www.geeksforgeeks.org/implementing-hash-table-open-addressing-linear-probing-cpp/
 
2) 제곱 탐사법(Quardratic Probing)
  • 빈 공간을 n 제곱만큼의 간격을 두고 탐사하는 방법
    • 충돌 발생 지점 부터 이후의 빈 공간을 n제곱 간격으로 탐사
  • 일차 군집화 문제 일부 보완
  • 이차 군집화 문제 발생 가능
https://www.codingeek.com/data-structure/complete-guide-open-addressing-classification-eliminate-collisions/
 
3) 이중 해싱(Double Hashing)
  • 해 함수(Hash Function)를 이중으로 사용
    • 해시 함수 1: 최초 해시를 구할 대 사용
    • 해시 함수 2: 충돌 발생 시, 탐사 간격을 구할 때 사용
  • 선형 탐사, 제곱 탐사에 비해 데이터가 골고루 분포
 

분리 연결법(Separate Chaining) - 해시 충돌 해결 방법(2)

  • 해시 테이블을 연결 리스트로 구성
  • 충돌 발생 시 , 테이블 내의 다른 위치를 탐색하는 것이 아닌 연결 리스트를 이용하여 해당 테이블에 데이터를 연결!
 

해시 선언/할당

 
Hashtable<String, Integer> ht = new Hashtable();
ht.put("key1", 10);
ht.put("key2", 20);
ht.put("key3", 30);

//출력
for(Map.Entry<String, Integer> item : ht.entrySet()){
	System.out.println(item.getKey) + "-" + item.getValue());
}
 

배열의 해시 충돌 세팅 방법

 

기본적인 Hash Table 기능 구현 Class

class MyHashTable {
    Integer[] table;
    int elemCnt; // 테이블안에 실질적으로 데이터 수 확인

    public MyHashTable() {
    }

    public MyHashTable(int size) {
        this.table = new Integer[size];
        this.elemCnt = 0;
    }

    public int getHash(int key){
        return key % table.length;
    }

    public void setValue(int key, int data){
        int idx = this.getHash(key);
        this.table[idx] = data;
        this.elemCnt++;
    }

    public int getValue(int key){
        int idx = this.getHash(key);
        return this.table[idx];
    }

    public void removeValue(int key){
        int idx = this.getHash(key);
        this.table[idx]= null;
        this.elemCnt--;
    }
    public void printHashTable(){
        System.out.println("== Hash Table ==");
        for(int i = 0; i <  this.table.length; i++){
            System.out.println(i + " : " + this.table[i]);
        }
        System.out.println();

    }
}
 

선형 탐사방법(Linear Probing)

class MyHashTable2 extends MyHashTable {

    MyHashTable2(int size) {
        super(size);
    }

    public void setValue(int key, int data) {
        int idx = this.getHash(key);

        if(this.elemCnt == this.table.length){
            System.out.println("Hash table is full!");
            return;
        } else if (this.table[idx] == null){
            this.table[idx] = data;
        } else {
            int newIdx = idx;
            while(true){
                newIdx = (newIdx + 1) % this.table.length;
                if(this.table[newIdx] == null){
                    break;
                }
            }
            this.table[newIdx] = data;
        }
        elemCnt++;
    }
}
 

제곱 탐사법(Quadratic Probing)

class MyHashTable3 extends MyHashTable {

    MyHashTable3(int size) {
        super(size);
    }

    public void setValue(int key, int data) {
        int idx = this.getHash(key);

        if (this.elemCnt == this.table.length) {
            System.out.println("Hash table is full!");
            return;
        } else if (this.table[idx] == null) {
            this.table[idx] = data;
        } else {
            int newIdx = idx;
            int cnt = 0;
            while (true) {
                newIdx = (int) (newIdx + Math.pow(2, cnt)) % this.table.length;
                if (this.table[newIdx] == null) {
                    break;
                }
                cnt++;
            }
            this.table[newIdx] = data;
        }
        this.elemCnt++;
    }
}
 

이중 해싱법(Double Hashing)

class MyHashTable4 extends MyHashTable {
    int c;

    MyHashTable4(int size) {
        super(size);
        this.c = this.getHashC(size);
    }

    public int getHashC(int size){
        int c = 0;
        if(size <= 2){
            return size; // 그 1~2 자체는 소수이기 때문에 바로 리턴
        }
        for(int i = size -1; i>2; i--){ // size -1 -> c가 table.length보다 작아야 하기 때문
            boolean isPrime = true;
            for(int j = 2; j<i; j++){
                if(i % j == 0){
                    isPrime = false; // 그 사이 수로 나누어 떨어지면 소수가 아니기 때문에
                    break;
                }
            }
            if(isPrime){
                c = i;
                break;
            }
        }
        return c;
    }

    public int getHash2(int key) {
        int hash = 1 + key % this.c; // c항 Hashtable보다 작으며 소수여야 한다.
        return hash;
    }

    public void setValue(int key, int data) {
        int idx = this.getHash(key);
        if (this.elemCnt == this.table.length) {
            System.out.println("Hash table sis full!");
        } else if (this.table[idx] == null) {
            this.table[idx] = data;
        } else {
            int newIdx = idx;
            int cnt = 1;
            while(true){
                newIdx = (newIdx + this.getHash2(newIdx) * cnt) % this.table.length;
                if(this.table[newIdx]==null){
                    break;
                }
                cnt++;
            }
            this.table[newIdx] = data;
        }
        this.elemCnt++;

    }
}
 

분리 연결법(Separate Chaining)

class Node {
    int key;
    int data;
    Node next;

    Node() {}
    Node(int key, int data, Node next) {
        this.key = key;
        this.data = data;
        this.next = next;
    }
}

class LinkedList {
    Node head;


    LinkedList() {}
    LinkedList(Node node) {
        this.head = node;
    }

    public boolean isEmpty() {
        return this.head == null;
    }

    public void addData(int key, int data) {
        if (this.head == null) {
            this.head = new Node(key, data, null);
        } else {
            Node cur = this.head;
            while (cur.next != null) {
                cur = cur.next;
            }
            cur.next = new Node(key, data, null);
        }
    }

    public void removeData(int data) {
        if (this.isEmpty()) {
            System.out.println("List is empty");
            return;
        }

        Node cur = this.head;
        Node pre = cur;
        while (cur != null) {
            if (cur.key == data) {
                if (cur == this.head) {
                    this.head = cur.next;
                } else {
                    pre.next = cur.next;
                }
                break;
            }

            pre = cur;
            cur = cur.next;
        }
    }

    public Integer findData(int key) {
        if (this.isEmpty()) {
            System.out.println("List is empty");
            return null;
        }

        Node cur = this.head;
        while (cur != null) {
            if (cur.key == key) {
                System.out.println("Data exist!");
                return cur.data;
            }
            cur = cur.next;
        }
        System.out.println("Data not found!");
        return null;
    }

    public void showData() {
        if (this.isEmpty()) {
            System.out.println("List is empty!");
            return;
        }

        Node cur = this.head;
        while (cur != null) {
            System.out.print(cur.data + " ");
            cur = cur.next;
        }
        System.out.println();
    }
}

class MyHashTable5 {

    LinkedList[] table;


    public MyHashTable5(int size) {
        this.table = new LinkedList[size];
        for(int i=0; i< this.table.length;i++){
            this.table[i] = new LinkedList(null);
        }
    }

    public int getHash(int key) {
        return key % this.table.length;
    }
    public void setValue(int key, int data){
        int idx = this.getHash(key);
        this.table[idx].addData(key, data);
    }

    public int getValue(int key)    {
        int idx = this.getHash(key);
        int data = this.table[idx].findData(key);
        return data;
    }

    public void removeValue(int key)    {
        int idx = this. getHash(key);
        this.table[idx].removeData(key);
    }

    public void printHashTable(){
        System.out.println("== Hash Table ==");
        for(int i = 0; i< this.table.length; i++)    {
            System.out.print(i + ": ");
            this.table[i].showData();
        }
    }
}