-
자료구조와 알고리즘, Hash TableData Structure & Algorithm 2024. 6. 30. 16:20
해시 테이블(Hash Table)은 키(Key)와 값(Value)의 쌍을 저장하고, 빠르게 데이터를 검색, 삽입, 삭제할 수 있는 자료 구조입니다. 해시 테이블은 해시 함수를 사용하여 키를 해시 값으로 변환하고, 이를 인덱스로 사용해 배열에 값을 저장합니다.
해시 테이블의 특징
- 빠른 검색, 삽입, 삭제: 평균적으로 O(1)의 시간 복잡도로 검색, 삽입, 삭제 작업을 수행할 수 있습니다.
- 해시 함수(Hash Function): 키를 입력받아 고정된 크기의 해시 값(주로 정수)을 반환하는 함수입니다. 좋은 해시 함수는 키를 고르게 분포시키고 충돌을 최소화합니다.
- 충돌(Collision): 서로 다른 키가 동일한 해시 값을 갖는 상황을 충돌이라고 합니다. 충돌을 해결하는 방법으로 체이닝(Chaining)과 개방 주소법(Open Addressing)이 있습니다.
충돌 해결 방법
- 체이닝(Chaining): 각 배열 요소를 연결 리스트로 관리하여, 충돌이 발생하면 해당 인덱스의 리스트에 노드를 추가하는 방식입니다.
- 개방 주소법(Open Addressing): 충돌이 발생하면 다른 빈 슬롯을 찾아 데이터를 저장하는 방식입니다. 대표적인 방법으로 선형 탐사(Linear Probing), 제곱 탐사(Quadratic Probing), 이중 해싱(Double Hashing) 등이 있습니다.
자바스크립트로 해시 테이블 구현하기
간단한 해시 함수 구현
function hash(key, arrayLen) { let total = 0; let WEIRD_PRIME = 31; for (let i = 0; i < Math.min(key.length, 100); i++) { let char = key[i]; let value = char.charCodeAt(0) - 96; total = (total * WEIRD_PRIME + value) % arrayLen; } return total; }
체이닝을 이용한 해시 테이블 구현
class HashTable { // 소수 값을 기본 값으로 설정한다 constructor(size = 53) { this.keyMap = new Array(size); } _hash(key) { let total = 0; // 소수를 곱하면, 단어간의 충돌 횟수가 현저히 줄어들었음 let WEIRD_PRIME = 31; // 키 값의 길이가 100이 넘는다면, 100을 사용하여 루프를 돈다 for (let i = 0; i < Math.min(key.length, 100); i++) { let char = key[i]; let value = char.charCodeAt(0) - 96; total = (total * WEIRD_PRIME + value) % this.keyMap.length; } return total; } set(key, value) { // Separate Chaining, Linear Probing 중 개별 체이닝 사용 // Accepts a key and a value // Hashes the key // Stores the key-value pair in the hash table array via separate chaining let index = this._hash(key); if (!this.keyMap[index]) { this.keyMap[index] = []; } this.keyMap[index].push([key, value]); } get(key) { // Accepts a key // Hashes the key // Retrieves the key-value pair in the hash table let index = this._hash(key); if (this.keyMap[index]) { for (let i = 0; i < this.keyMap[index].length; i++) { if (this.keyMap[index][i][0] === key) { return this.keyMap[index][i]; } } } return undefined; } keys() { let results = []; for (let key of this.keyMap) { if (key) { for (let [key, value] of key) { if (!results.includes(key)) results.push(key); } } } return results; } values() { let results = []; for (let key of this.keyMap) { if (key) { for (let [key, value] of key) { if (!results.includes(value)) results.push(value); } } } return results; } } let newHashTable = new HashTable(); newHashTable.set('bowow', 'A'); newHashTable.set('cowow', 'B'); newHashTable.set('dowow', 'C'); newHashTable.set('Awwww', 'D'); newHashTable.set('Awwww', 'D'); newHashTable.set('Awwww', 'D'); newHashTable.get('Awwww'); console.log(newHashTable.keys()); console.log(newHashTable.values()); /** * newHashTable: HashTable { keyMap: [ <19 empty items>, [ [Array] ], <3 empty items>, [ [Array], [Array] ], <3 empty items>, [ [Array] ], <25 empty items> ] } */
선형 탐사를 통한 구현
class HashTable { constructor(size = 53) { this.keyMap = new Array(size); } _hash(key) { let total = 0; let WEIRD_PRIME = 31; for (let i = 0; i < Math.min(key.length, 100); i++) { let char = key[i]; let value = char.charCodeAt(0) - 96; total = (total * WEIRD_PRIME + value) % this.keyMap.length; } return total; } set(key, value) { let index = this._hash(key); // 선형 탐사를 사용하여 빈 슬롯을 찾음 while (this.keyMap[index] !== undefined && this.keyMap[index].key !== key) { index = (index + 1) % this.keyMap.length; } this.keyMap[index] = { key, value }; } get(key) { let index = this._hash(key); // 선형 탐사를 사용하여 키를 찾음 while (this.keyMap[index] !== undefined) { if (this.keyMap[index].key === key) { return this.keyMap[index].value; } index = (index + 1) % this.keyMap.length; } return undefined; } keys() { let keysArr = []; for (let i = 0; i < this.keyMap.length; i++) { if (this.keyMap[i]) { keysArr.push(this.keyMap[i].key); } } return keysArr; } values() { let valuesArr = []; for (let i = 0; i < this.keyMap.length; i++) { if (this.keyMap[i]) { valuesArr.push(this.keyMap[i].value); } } return valuesArr; } } // 사용 예시 let ht = new HashTable(17); ht.set("maroon", "#800000"); ht.set("yellow", "#FFFF00"); ht.set("olive", "#808000"); ht.set("salmon", "#FA8072"); ht.set("lightcoral", "#F08080"); ht.set("mediumvioletred", "#C71585"); ht.set("plum", "#DDA0DD"); console.log(ht.get("yellow")); // #FFFF00 console.log(ht.keys()); // ["maroon", "yellow", "olive", "salmon", "lightcoral", "mediumvioletred", "plum"] console.log(ht.values()); // ["#800000", "#FFFF00", "#808000", "#FA8072", "#F08080", "#C71585", "#DDA0DD"]
해시 테이블의 장점
- 빠른 데이터 접근: 평균적으로 O(1) 시간 복잡도로 데이터 접근이 가능합니다.
- 간단한 구현: 비교적 간단하게 구현할 수 있으며, 대부분의 프로그래밍 언어에서 내장 자료 구조로 제공됩니다.
해시 테이블의 단점
- 충돌 처리: 충돌이 발생하면 성능이 저하될 수 있으며, 이를 해결하기 위한 추가적인 구조가 필요합니다.
- 공간 낭비: 해시 테이블의 크기를 적절하게 설정하지 않으면 메모리 낭비가 발생할 수 있습니다.
- 정렬되지 않은 데이터: 데이터가 특정 순서로 저장되지 않기 때문에 정렬된 데이터를 제공해야 하는 경우에는 적합하지 않습니다.
해시 테이블의 응용
- 캐싱: 데이터베이스 조회 결과 등을 캐싱하여 성능을 향상시킬 때 사용됩니다.
- 집합 연산: 집합의 포함 여부를 빠르게 확인할 수 있는 자료 구조로 사용됩니다.
- 연관 배열: 키와 값을 맵핑하는 연관 배열이나 사전을 구현하는 데 사용됩니다.
해시 테이블은 매우 강력하고 효율적인 자료 구조로, 많은 알고리즘과 시스템에서 필수적인 역할을 합니다.
'Data Structure & Algorithm' 카테고리의 다른 글
알고리즘, graph, Dijkstra (0) 2024.07.03 자료구조와 알고리즘, graph, dfs, bfs (0) 2024.07.01 자료구조와 알고리즘, Tree, Priority Queue (0) 2024.06.28 자료구조와 알고리즘, Tree, Binary Heap (0) 2024.06.28 알고리즘, Tree, DFS (0) 2024.06.27