21-03-12 자바스크립트로 하는 자료구조와 알고리즘(6)
11장 해시테이블
- 해시 테이블은 키-값 쌍을 기반으로 자료를 얻을 수 있다
- 해시 테이블에는 put()과 get()의 두 함수가 있어 자료를 저장하고 얻어올 수 있다
- localStorage는 해시 테이블에 기반한 자료구조로 모든 브라우저가 지원하는 기본 자바스크립트 객체이다
- 해시 테이블에서 가장 중요한건 해시 함수로 이 함수는 특정 키를 자료를 저장하는 배열의 인덱스로 변환한다
- 좋은 해시 함수는 동일한 키는 동일한 해시값을 생성해야 하며(결정성), 시간 복잡도는 O(1)이어야 하며(효율성) 배열 전체를 최대한 활용해야 한다(균일한 분배)
- 해싱의 첫번째 기법은 소수를 사용하는 것 이다
- 소수에 의한 모듈러는 고정된 크기에 대해 가장 균등한 분배를 보장한다
- 소수 모듈러로 나눈 나머지 값으로 인덱싱하는 해시 테이블에서 같은 값이 있다면 충돌이 일어나게 되는데 이를 회피하기 위하여 탐사 해싱 기법을 이용한다
- 선형 탐사는 만약 같은 키로 해싱된다면 그 다음 빈 곳을 찾아 해싱한다
- 이 때 get(key)함수를 사용한다면 충돌된 부분을 찾은 후에 해당 값을 찾고자 테이블을 순회한다
- 이차 탐사는 선형 탐사와는 달리 1씩 증가시키지 않고 완전 제곱을 사용하여 사용 가능한 인덱스에 키를 균등하게 분배한다
- 이차 해싱 함수를 사용해 원래 해싱 함수로부터 나온 결과를 한 번 더 해싱하면 키를 균일하게 분배할 수 있다
- 해시 테이블 구현 (교재에는 옛날 방식으로 구현되어 있어서 class 문법을 사용하였음)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43class HashTable {
constructor(size) {
this.size = size;
this.keys = this.initArray(size);
this.values = this.initArray(size);
this.limit = 0;
}
put(key, value) {
if (this.limit >= this.size)
throw 'hash table is full';
let hashedIndex = this.hash(key);
while (this.keys[hashedIndex] != null) {
hashedIndex++
hashedIndex = hashedIndex % this.size;
}
this.keys[hashedIndex] = key;
this.values[hashedIndex] = value;
this.limit++
}
get(key) {
let hashedIndex = this.hash(key);
while (this.keys[hashedIndex] != key) {
hashedIndex++
hashedIndex = hashedIndex % this.size;
}
return this.value[hashedIndex];
}
hash(key) {
if(!Number.isInteger(key))
throw 'must be number';
return key % this.size;
}
initArray(size) {
return Array.from({length: size}, () => null);
}
} - 이차 탐사 사용하기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43class HashTable {
constructor(size) {
this.size = size;
this.keys = this.initArray(size);
this.values = this.initArray(size);
this.limit = 0;
}
put(key, value) {
if (this.limit >= this.size)
throw 'hash table is full';
let hashedIndex = this.hash(key);
while (this.keys[hashedIndex] != null) {
hashedIndex++
hashedIndex = hashedIndex % this.size;
}
this.keys[hashedIndex] = key;
this.values[hashedIndex] = value;
this.limit++
}
get(key) {
let hashedIndex = this.hash(key);
while (this.keys[hashedIndex] != key) {
hashedIndex++
hashedIndex = hashedIndex % this.size;
}
return this.value[hashedIndex];
}
hash(key) {
if(!Number.isInteger(key))
throw 'must be number';
return key % this.size;
}
initArray(size) {
return Array.from({length: size}, () => null);
}
} - 이차 탐사 (put, get 함수만)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24put(key, value) {
if (this.limit >= this.size)
throw 'hash table is full';
let hashedIndex = this.hash(key), squareIndex = 1;
while (this.keys[hashedIndex % this.size] != null) {
hashedIndex += Math.pow(squareIndex, 2);
squareIndex++;
}
this.keys[hashedIndex % this.size] = key
this.value[hashedIndex % this.size] = value;
this.limit++
}
get(key) {
let hashedIndex = this.hash(key);
while (this.keys[hashedIndex] != key) {
hashedIndex++
hashedIndex = hashedIndex % this.size;
}
return this.value[hashedIndex];
} - 선형 탐사를 활용한 이중 해싱
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35put(key, value) {
if (this.limit >= this.size)
throw 'hash table is full';
let hashedIndex = this.hash(key);
while (this.keys[hashedIndex] != null) {
hashedIndex++;
hashedIndex = hashedIndex % this.size;
}
this.keys[hashedIndex] = key
this.value[hashedIndex] = value;
this.limit++
}
get(key) {
let hashedIndex = this.hash(key);
while (this.keys[hashedIndex] != key) {
hashedIndex++
hashedIndex = hashedIndex % this.size;
}
return this.value[hashedIndex];
}
hash(key) {
if (!Number.isInteger(key))
throw 'must be number'
return this.secondHash(key);
}
secondHash(hashedKey) {
let R = this.size - 2;
return R - hashedKey % R;
} - 해시테이블은 크기가 처음에 정의되는(size를 매개변수로 생성자에 전달한 것을 보자) 고정된 크기의 자료구조이다
- 해시 함수를 사용해 균등하게 분배하고 충돌이 일어나는 것을 최대한 막아야한다
12장 스택과 큐
- 스택은 마지막에 삽입된 항목만을 제거하고 접근할 수 있다
- 자바스크립트에서 배열에는 스택 클래스를 정의한 pop과 push라는 메서드가 있다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26class Stack {
constructor (arr) {
this.arr = [];
if(arr) this.arr = arr;
}
getBuffer() {
return this.arr.slice();
}
isEmpty() {
return this.arr.length === 0;
}
peek() {
return this.arr[this.arr.length-1];
}
push(value) {
this.arr.push(value);
}
pop() {
return this.arr.pop();
}
} - 위에서부터 n번째 노드에 접근하기 위해 pop을 n번 호출해야 한다
1
2
3
4
5
6
7
8
9
10
11const stackAccessNthTopNode = (stack, n) => {
let bufferArr = stack.getBuffer();
if (n <= 0) throw 'error';
let bufferStack = new Stack(bufferArr);
while(--n! === 0)
bufferStack.pop();
return bufferStack.pop();
} - 검색 또한 이와 비슷한 방식으로 구현할 수 있다
1
2
3
4
5
6
7
8const stackSearch = (stack, element) => {
let bufferArr = stack.getBuffer();
let bufferStack = new Stack(bufferArr);
while(!bufferStack.isEmpty()){
if (bufferStack.pop() === element) return true;
}
return false;
} - 큐는 스택과 달리 첫번째로 추가도니 항목만을 제거할 수 있는 자료 구조이다
- 자바스크립트에서는 shift()와 push()라는 메서드가 있어 이 함수를 호출하면 배열의 첫번째 항목을 제거해 리턴한다
- 큐에 추가하는 것을 인큐(enqueuing)라고, 제거하는 것을 디큐(dequeuing)라 한다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26class Queue {
constructor (arr) {
this.arr = [];
if(arr) this.arr = arr;
}
getBuffer() {
return this.arr.slice();
}
isEmpty() {
return this.arr.length === 0;
}
peek() {
return this.arr[0]; // 제거하지 않고 값만 리턴
}
enqueue(value) {
return this.arr.push(value);
}
dequeue() {
return this.arr.shift();
}
} - 큐에 접근할 때엔 인덱스로 접근할 수 없다 n번째 노드에 접근하려면 디큐를 n번 호출해야 한다
1
2
3
4
5
6
7
8
9
10
11const queueAccessNthTopNode = (queue, n) => {
let bufferArr = queue.getBuffer();
if (n <= 0) throw 'error'
let bufferQueue = new Queue(bufferArr);
while(--n !== 0)
bufferQueue.dequeue(); // 필요한 노드 직전까지 디큐
return bufferQueue.dequeue(); // 리턴으로 디큐하면 해당 노드가 필요한 노드
} - 스택을 사용해 큐를 구현하기
→ 두개의 스택을 사용한다면 큐를 만들 수 있다1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19class TwoStackQueue {
constructor() {
this.inbox = new Stack();
this.outbox = new Stack();
}
enqueue(value) {
this.inbox.push(value);
}
dequeue() {
if(this.outbox.isEmpty()) {
while(!this.inbox.isEmpty()){
this.outbox.push(this.inbox.pop());
}
}
return this.outbox.pop();
}
} - 큐를 사용해 스택을 구현하기도 마찬가지로 두 개의 큐를 사용하면 스택을 구현할 수 있다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20class QueueStack {
constructor() {
this.inbox = new Queue();
}
push(value) {
this.inbox.enqueue(value);
}
pop() {
let size = this.inbox.arr.length - 1;
let cnt = 0;
let bufferQueue = new Queue();
while(++counter <= size)
bufferQueue.enqueue(this.inbox.dequeue());
let popped = this.inbox.dequeue();
this.inbox = bufferQueue;
return popped;
}
} - 고객 객체를 매개변수로 받아 선입선출(Queue) 방식으로 음식을 주문 처리하는 점원 클래스를 설계
- 점원은 주문을 위해 고객의 이름과 주문 항목을 요구
- 첫번째로 받은 주문을 먼저 처리함
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18class Customer {
constructor (name, order) {
this.name = name;
this.order = order;
}
addOrder (customer) {
this.customers.enqueue(customer);
}
deliverOrder() {
let finishedCustomer = this.customers.dequeue();
}
}
function Cashier() {
this.customers = new Queue();
}
- 스택을 사용해 괄호 검증기를 설계하기
1
2
3
4
5
6
7
8
9
10
11
12
13const BracketVerification = string => {
let stack = new Stack();
for (let pos = 0; pos < string.length; pos++) {
let currentChar = string.charAt(pos);
if(currentChar === "("){
stack.push(currentChar);
} else if (currentChar ===")") {
if(stack.isEmpty()) return false;
stack.pop();
}
}
return stack.isEmpty();
} - 정렬 가능한 스택 설계하기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20class sortableStack {
constructor (size) {
this.size = size;
this.mainStack = new Stack();
this.sortedStack = new Stack();
for (let i = 0; i < this.size; i++) {
this.mainStack.push(Math.floor(Math.random() * 11));
}
}
sortStackDescending() {
while(!this.mainStack.isEmpty()){
let temp = this.mainStack.pop();
while(!this.sortedStack.isEmpty && this.sortedStack.peek() <temp) {
this.mainStack.push(this.sortedStack.pop());
}
this.sortedStack.push(temp)
}
}
}