해시테이블
- 먼저 해시함수란?
- 임의의 길이의 데이터를 고정된 길이의 데이터로 매핑하는 함수
- 해시 함수 특성
- 압축성 : 다양한 가변 길이의 입력에 대해 고정된 크기의 결과값을 반환하는 성질
- 효율성 : 어떤 입력 값에 대해서도 많은 자원과 시간이 소요되지 않고 처리되는 성질
- 저항성 : 결과값을 바탕으로 입력 값을 찾는 것이 불가능한 성질
- 해시테이블
- Hash 함수를 사용하여 평균 O(1) 시간 복잡도로 특정 값을 신속하게 찾는 자료 구조
- 충돌(Collision) 해결방법
- 해시 함수 변경 : 더 큰 숫자의 공간과 Modular 산술 값을 이용해 충돌 최소화
- 자료구조 확장 : Open Addressing Method(선형 조사법, 이중 해시), Close Addressing Method(체이닝)
const HASH_SIZE = 37;
function Element(key, value) {
this.key = key;
this.value = value;
}
function HashTable() {
this.table = new Array(HASH_SIZE);
this.length = 0;
}
HashTable.prototype.hashCode = function (key) {
let hash = 0;
for (let i = 0; i < key.length; i++) {
hash += key.charCodeAt(i);
}
return hash % HASH_SIZE;
};
HashTable.prototype.put = function (key, value) {
let index = this.hashCode(key);
console.log(`key: ${key} -> index: ${index}`);
if (this.table[index] !== undefined) {
return false;
}
this.table[index] = new Element(key, value);
this.length++;
return true;
};
HashTable.prototype.get = function (key) {
return this.table[this.hashCode(key)];
};
HashTable.prototype.remove = function (key) {
let element = this.table[this.hashCode(key)];
if (element !== undefined) {
delete this.table[this.hashCode(key)];
this.length--;
}
return element;
};
HashTable.prototype.clear = function () {
this.table = new Array(HASH_SIZE);
this.length = 0;
};
HashTable.prototype.size = function () {
return this.length;
};
HashTable.prototype.getBuffer = function () {
let array = [];
for (let i = 0; i< this.table.length; i++) {
if (this.table[i]) {
array.push(this.table[i]);
}
}
return array;
};
HashTable.prototype.print = function () {
for (let i = 0; i < this.table.length; i++) {
if (this.table[i]) {
console.log(i + " -> " + this.table[i].key + ": " + this.table[i].value);
}
}
};
- 충돌해결, 해시함수 변경을 통해서 충돌을 최소화함
const HASH_SIZE = 37;
HashTable.prototype.hashCode = function (key) {
let hash = 0;
for (let i = 0; i < key.length; i++) {
hash += key.charCodeAt(i);
}
return hash % HASH_SIZE;
};
const HASH_SIZE = 1013;
HashTable.prototype.hashCode = function (key) {
let hash = 5381;
for (let i = 0; i < key.length; i++) {
hash = hash * 33 + key.charCodeAt(i);
}
return hash % HASH_SIZE;
};
선형 조사법 해시테이블
- Hash 충돌이 발생했을 때, 그 다음 주소를 확인하고 비어 있다면 그 자리에 대신 저장하는 해시테이블 기반 자료구조
cosnt HASH_SIZE = 5;
function Element(key, value) {
this.key = key;
this.value = value;
}
function LinearHashTable() {
this.table = new Array(HASH_SIZE);
this.length = 0;
}
LinearHashTable.prototype.hashCode = function (key) {
let hash = 0;
for (let i = 0; i < key.length; i++) {
hash += key.charCodeAt(i);
}
return hash % HASH_SIZE;
};
LinearHashTable.prototype.clear = function () {
this.table = new Array(HASH_SIZE);
this.length = 0;
};
LinearHashTable.prototype.size = function () {
return this.length;
};
LinearHashTable.prototype.getBuffer = function () {
let array = [];
for (let i = 0; i < this.table.length; i++) {
if (this.table[i]) {
array.push(this.table[i]);
}
}
return array;
};
LinearHashTable.prototype.print = function () {
for (let i = 0; i < this.table.length; i++) {
if (this.table[i]) {
console.log(i + " -> " + this.table[i].key + ": " + this.table[i].value);
}
}
};
LinearHashTable.prototype.put = function (key, value) {
let index = this.hashCode(key);
let startIndex = index;
console.log(`key: ${key} -> index: ${index}`);
do {
if (this.table[index] === undefined) {
this.table[index] = new Element(key, value);
this.length++;
return true;
}
index = (index + 1) % HASH_SIZE;
} while (index !== startIndex);
return false;
};
LinearHashTable.prototype.get = function (key) {
let index = this.hashCode(key);
let startIndex = index;
do {
if (this.table[index] !== undefined && this.table[index].key === key) {
return this.table[index].value;
}
index = (index + 1) % HASH_SIZE;
} while (index !== startIndex);
return undefined;
};
LinearHashTable.prototype.remove = function (key) {
let index = this.hashCode(key);
let startIndex = index;
do {
if (this.table[index] !== undefined && this.table[index].key === key) {
let element = this.table[index];
delete this.table[index];
this.length--;
return element;
}
index = (index + 1) % HASH_SIZE;
} while (index !== startIndex);
return undefined;
};
체이닝 해시테이블
- 별도의 자료구조인 연결리스트를 병합 사용하여 Hash 충돌을 해결한 해시테이블 기반 자료구조
const HASH_SIZE = 37;
function Element(key, value) {
this.key = key;
this.value = value;
}
function ChainingHashTable() {
this.table = new Array(HASH_SIZE);
this.length = 0;
}
ChainingHashTable.prototype.hashCode = function (key) {
let hash = 0;
for (let i = 0; i < key.length; i++) {
hash += key.charCodeAt(i);
}
return hash % HASH_SIZE;
};
ChainingHashTable.prototype.clear = function () {
this.table = new Array(HASH_SIZE);
this.length = 0;
};
ChainingHashTable.prototype.size = function () {
return this.length;
};
ChainingHashTable.prototype.put = function (key, value) {
let index = this.hashCode(key);
console.log(`key: ${key} -> index: ${index}`);
if (this.table[index] === undefined) {
this.table[index] = new LinkedList();
}
this.table[index].append(new Element(key, value));
this.length++;
return true;
};
ChainingHashTable.prototype.getBuffer = function () {
let array = [];
for (let i = 0; i < this.table.length; i++) {
if (this.table[i]) {
let current = this.table[i].head;
do {
array.push(current.data);
current = current.next;
} while (current);
}
}
return array;
};
ChainingHashTable.prototype.print = function () {
for (let i = 0; i < this.table.length; i++) {
if (this.table[i]) {
let current = this.table[i].head;
process.stdout.write(`#${i}`);
do {
process.stdout.write(` -> ${current.data.key}: ${current.data.value}`);
current = current.next;
} while (current);
console.log("");
}
}
};
ChainingHashTable.prototype.get = function (key) {
let index = this.hashCode(key);
if (this.table[index] !== undefined && !this.table[index].isEmpty()) {
let current = this.table[index].head;
do {
if (current.data.key === key) {
return current.data.value;
}
current = current.next;
} while (current);
}
return undefined;
};
ChainingHashTable.prototype.remove = function (key) {
let index = this.hashCode(key);
let element = undefined;
if (this.table[index] !== undefined) {
let current = this.table[index].head;
do {
if (current.data.key === key) {
element = current.data;
this.table[index].remove(current.data);
if (this.table[index].isEmpty()) {
delete this.table[index];
}
}
current = current.next;
} while (current);
}
this.length--;
return element;
};