728x90

해싱(Hashing)은 데이터 관리를 위한 중요한 기법 중 하나이다. 해싱은 어떤 입력값을 고정된 길이의 값으로 변환하는 과정을 말하며, 이 때 사용되는 함수를 해시 함수라 한다. 해시 함수는 다양한 길이의 입력을 받아서 고정된 길이의 해시값을 출력한다. 이 해시값은 데이터의 저장 위치를 결정하는 데 사용된다.

 

해시 테이블(Hash Table)은 해싱 기법을 사용하여 데이터를 저장하는 자료구조이다. 각 데이터는 해시 함수를 통해 계산된 해시값에 따라 해시 테이블 내의 특정 위치에 저장된다. 해시 테이블은 키-값 쌍으로 데이터를 저장하며, 이때 키를 해시 함수에 입력하여 그 결과로 나온 해시값을 사용하여 값을 저장하거나 검색한다.

 

해시 테이블의 주요 장점은 데이터의 추가, 삭제, 검색 작업을 평균적으로 상수 시간(O(1)) 내에 수행할 수 있다는 점이다. 하지만 최악의 경우(모든 키가 동일한 위치에 해시되는 경우)에는 모든 데이터를 검색해야 하므로 성능이 크게 저하될 수 있다.

 

Python의 dict, Java의 HashMap 같은 프로그래밍 언어의 내장 자료구조도 내부적으로 해시 테이블을 사용한다. Python에서는 hash() 함수를 사용하여 객체의 해시값을 얻을 수 있다. 이 함수는 내장 데이터 타입(예: 정수, 문자열)에 대해 해시값을 반환한다. 하지만 사용자 정의 객체의 경우, 객체가 해시 가능하도록 __hash__() 메소드를 구현해야 한다.

 

또한, Python의 dict 타입은 내부적으로 해시 테이블을 사용하여 키-값 쌍을 저장한다. 이를 통해 키에 대한 빠른 접근과 검색, 추가, 삭제 작업을 지원한다.

# 정수의 해시값
hash_value = hash(100)
print(hash_value)

# 문자열의 해시값
hash_value = hash("Hello, World!")
print(hash_value)
class Student:
    def __init__(self, id, name):
        self.id = id
        self.name = name

    # 객체가 해시 가능하도록 __hash__ 메소드 구현
    def __hash__(self):
        return hash((self.id, self.name))

    # __eq__ 메소드도 구현하여 객체의 동등성 비교 가능하게 함
    def __eq__(self, other):
        return self.id == other.id and self.name == other.name

# 사용자 정의 객체 생성
student = Student(1, "John Doe")

# 사용자 정의 객체의 해시값
print(hash(student))

__hash__() 메소드는 객체의 해시값을 계산하여 반환하며, __eq__() 메소드는 두 객체의 동등성을 비교한다. Python에서는 두 객체가 동등하다고 판단되면, 이들의 해시값도 동일해야 한다.

 

해시 충돌(Collision)두 개 이상의 키가 해시 함수를 통해 동일한 해시값(즉, 동일한 위치)을 가리키는 경우 발생하는 현상이다. 해시 테이블의 효율성을 저하시키며, 이를 효과적으로 처리하는 것이 해시 테이블 설계의 핵심적인 부분이다. 충돌이 발생했을 때 체이닝과 재해싱은 이를 해결하기 위한 두 가지 주요 방법이다.

 

체이닝(Chaining)은 충돌이 발생했을 때 같은 해시값을 가진 요소들을 연결 리스트와 같은 자료구조를 사용하여 연결하는 방식이다. 이렇게 하면 한 버킷에 여러 요소가 저장될 수 있으며, 충돌로 인한 문제를 효율적으로 해결할 수 있다. 체이닝 방식의 장점은 해시 테이블의 크기와 무관하게 충돌을 관리할 수 있다는 점이며, 구현이 비교적 간단하다는 것도 장점이다. 다만, 체인이 길어지면 검색 성능이 저하될 수 있다.

class HashTableChaining:
    def __init__(self, size=10):
        self.size = size
        self.table = [[] for _ in range(size)]
    
    def hash_function(self, key):
        return key % self.size
    
    def insert(self, key, value):
        hash_index = self.hash_function(key)
        for i, (k, v) in enumerate(self.table[hash_index]):
            if k == key:
                self.table[hash_index][i] = (key, value)
                return
        self.table[hash_index].append((key, value))
    
    def search(self, key):
        hash_index = self.hash_function(key)
        for k, v in self.table[hash_index]:
            if k == key:
                return v
        return None
    
    def delete(self, key):
        hash_index = self.hash_function(key)
        for i, (k, v) in enumerate(self.table[hash_index]):
            if k == key:
                del self.table[hash_index][i]
                return True
        return False

 

 

재해싱(rehashing)은 해시 테이블의 크기를 조정하고, 모든 요소를 새로운 크기에 맞는 해시 함수를 사용하여 재배치하는 방법이다. 주로 해시 테이블의 사용률(로드 팩터)이 높아져 성능 저하가 예상될 때 사용된다. 재해싱은 충돌의 가능성을 줄이고, 해시 테이블의 성능을 유지하는 데 도움이 된다. 재해싱 과정은 모든 요소를 새로운 해시 테이블로 이동시켜야 하므로, 리소스와 시간이 많이 소모되는 작업이다. 그래서, 재해싱은 해시 테이블의 성능을 개선하기 위해 필요할 때만 수행되어야 한다.

class HashTableRehashing:
    def __init__(self, size=10):
        self.size = size
        self.count = 0
        self.table = [None] * size
    
    def hash_function(self, key):
        return key % self.size
    
    def rehash(self, key, step=1):
        return (key + step) % self.size
    
    def resize_and_rehash_all(self):
        old_table = self.table[:]
        self.size *= 2
        self.table = [None] * self.size
        self.count = 0
        for item in old_table:
            if item:
                key, value = item
                self.insert(key, value)
    
    def insert(self, key, value):
        if self.count / self.size >= 0.7:  # Load factor threshold
            self.resize_and_rehash_all()
        
        hash_index = self.hash_function(key)
        step = 1
        while self.table[hash_index] is not None:
            if self.table[hash_index][0] == key:
                self.table[hash_index] = (key, value)
                return
            hash_index = self.rehash(hash_index, step)
            step += 1
        
        self.table[hash_index] = (key, value)
        self.count += 1
    
    def search(self, key):
        hash_index = self.hash_function(key)
        step = 1
        while self.table[hash_index] is not None:
            if self.table[hash_index][0] == key:
                return self.table[hash_index][1]
            hash_index = self.rehash(hash_index, step)
            step += 1
        return None
    
    def delete(self, key):
        hash_index = self.hash_function(key)
        step = 1
        while self.table[hash_index] is not None:
            if self.table[hash_index][0] == key:
                self.table[hash_index] = None
                self.count -= 1
                return True
            hash_index = self.rehash(hash_index, step)
            step += 1
        return False

 

위 코드에선 해시 테이블이 일정 수준으로 채워지면(load factor >= 0.7), 해시 테이블의 크기를 늘리고 모든 요소를 새로운 크기에 맞게 재해싱하여 충돌 가능성을 줄인다.

728x90

'CS > 자료구조' 카테고리의 다른 글

B-tree, B+tree, Trie, Minimum Spanning Tree  (1) 2024.04.01
Graph, Adjacency Matrix, Adjacency List  (0) 2024.03.31
Array, Linked List  (2) 2024.03.28
Red-Black Tree 구현  (0) 2024.03.22
이진탐색트리, AVL트리  (0) 2024.03.21

+ Recent posts