해시 테이블
해시 함수로 해싱
//Quickasaurus
//단어를 입력하면 유의어 하나를 반환하는 함수를 구현한다고 하자.
//배열과 유사하게 해시 테이블은 내부적으로 데이터를 한 줄로 이루어진 셀 묶음에 저장한다.
thesaurus = {}
thesaurus['bad'] = 'evil' //이 항목을 해시 테이블에 저장하려고 할때
BAD = 2 * 1 * 4 = 8 //해시 함수는 bad라는 글자를 정해진 규칙에 의해 해싱하여 인덱스 값을 구한다.
셀 8에 'evil'이라는 글자가 저장되고, 이제 'bad'라는 글자가 입력되었을때 해싱 과정을 거쳐 바로 'evil'이라는 글자를 구할 수 있다.
충돌 해결
thesaurus['dab'] = 'pat' //이와 같은 키/값을 해시 테이블에 저장하려고 할때
DAB = 4 * 1 * 2 = 8 //이미 입력된 키/값('bad' : 'evil')과 충돌하는 문제가 발생한다.
효율적인 해시 테이블 만들기
해시 테이블로 속도 올리기
해시 테이블은 쌍으로 된 데이터와 완벽하게 들어 맞지만 쌍이 아닌 데이터라도 코드르 빠르게 만들때 쓰일 수 있다.
const array = [61, 30, 91, 11]
//위 배열에서 어떤 수를 찾으려면 N단계가 걸리지만
const object = {
61: true,
30: true,
91: true,
11: true,
}
//위 와 같은 해시 테이블에서는 한단계가 걸린다.
//두 배열의 입력받아 중첩 루프를 통해 부분 집합인지 여부를 리턴하는 함수
function isSubset(array1, array2) {
let largerArray;
let smallerArray;
if(array1.length > array2.length) {
largerArray = array1;
smallerArray = array2;
} else {
largerArray = array2;
smallerArray = array1;
}
for(let i = 0; i < smallerArray.length; i++) {
let foundMatch = false;
for(let j = 0; j < largerArray.length; j++) {
if(smallerArray[i] === largerArray[j]) {
foundMatch = true;
break;
}
}
if(foundMatch === false) { return false; }
}
return true;
}
//해시 테이블을 사용하여 한 배열이 다른 배열의 부분 집합인지 여부를 반환하는 함수
function isSubset(array1, array2) {
let largerArray;
let smallerArray;
let hashTable = {};
if(array1.length > array2.length) {
largerArray = array1;
smallerArray = array2;
} else {
largerArray = array2;
smallerArray = array1;
}
for(const value of largerArray) {
hashTable[value] = true;
}
for(const value of smallerArray) {
if(!hashTable[value]) { return false; }
}
return true;
}
위 예시에서 배열들의 루프를 사용해서 부분 집합 여부를 리턴하는 함수는 N * M 단계가 걸리지만 해시 테이블을 활용하면 N단계가 걸린다.
스택(Stack)
스택은 데이터를 끝에만 삽입하고 삭제할 수 있는 LIFO(Last In, First Out)의 자료구조로, 가장 나중에 들어온 데이터가 가장 먼저 나간다.
스택은 구현하는데 어떤 데이터 구조를 쓰든 개의치 않고, 작동하는 규칙 집합으로 이루어진 추상 데이터 타입이다.
//부트캠프에서 풀었던 스택을 활용한 코딩 문제
function balancedBrackets (str) {
const compareArr = '(){}[]';
if(str === '') return true;
if(str.length % 2 !== 0) return false;
const storage = [];
for(let i = 0; i < str.length; i++) {
let compareIdx = compareArr.indexOf(str[i])
if(compareIdx % 2 === 0) {
storage.unshift(compareIdx + 1)
} else {
if(storage.shift() !== compareIdx) return false
}
}
return true;
};
가장 나중에 열린 괄호가 가장 먼저 닫혀야 하는 스택과 같은 특성을 활용하여 올바른 괄호인지 검사하는 함수에 활용할 수 있다.
큐(Queue)
큐는 가장 먼저 삽입된 데이터가 가장 먼저 삭제되는 FIFO(First In, First Out) 구조의 자료구조이다.
//부트캠프에서 큐를 활용해 풀었던 인쇄 작업 목록의 최대 용량이 정해져 있는 프린트의 최소 인쇄 시간을 리턴하는 문제
function queuePrinter(bufferSize, capacities, documents) {
let time = 0;
let buffer = Array(bufferSize).fill(0);
let volum = 0;
let current = documents.shift();
buffer.push(current);
buffer.shift();
volum += current;
time++;
while(volum){
volum -= buffer.shift();
current = documents.shift();
if(current+volum <= capacities){
buffer.push(current);
volum += current;
}
else {
buffer.push(0);
documents.unshift(current);
}
time++;
}
return time;
}
재귀
재귀는 함수가 자기 자신을 호출할 때를 뜻하는 용어로 재귀를 올바르게 사용하면 까다로운 문제 유형을 놀랍도록 간단하게 풀 수 있다.
//루프를 사용하여 number부터 0까지 숫자를 출력하는 함수
function countdown(number) {
for(let i=number; i>=0; i++) {
console.log(i)
}
}
//재귀를 사용하여 10부터 0
function countdown(number) {
console.log(number);
if(number === 0) {
return;
} else {
countdown(number - 1);
}
}
루프를 사용할 수 있느 경우라면 대부분 재귀를 사용할 수 있다.
if(number === 0)
처럼 함수가 무한히 반복되지 않는 조건을 기저조건(base case)이라고 하며 모든 재귀함수는 기저 조건이 적어도 하나가 있어야 한다.
호출 스택
//다차원 배열을 입력받아 1차원 배열로 변환하여 리턴하는 함수
function flattenArr(arr) {
if(arr.length === 0) return []
if(Array.isArray(arr[0])) return flattenArr([...arr[0],...arr.slice(1)]);
return [arr[0],...flattenArr(arr.slice(1))];
}
대표적인 자료구조들과 재귀에 대해서 공부하고 예전에 풀었던 코딩 문제들을 다시 살펴보면서 공부했던 내용을 복습할 수 있었다. 자료구조와 알고리즘에 대한 개념적인 이해도 중요하지만 결국 어떻게 현업이나 프로젝트에서 활용할 수 있는가에 대한 부분이 중요하다는 생각이 들었다. 부트캠프를 진행한 이후에는 코딩 문제를 놓았었기 때문에 감을 잃지 않기 위해 꾸준하게 코딩 테스트 문제를 풀어야겠다는 생각이 든다.