diff --git a/problem-1/SymbolTableWithArray.js b/problem-1/SymbolTableWithArray.js index 761c9ef..59ef57b 100644 --- a/problem-1/SymbolTableWithArray.js +++ b/problem-1/SymbolTableWithArray.js @@ -1,4 +1,58 @@ class SymbolTable { + #keys = []; + #values = []; + #n = 0; + + constructor(maxCount = 10) { + this.#keys = new Array(maxCount); + this.#values = new Array(maxCount); + } + + get(key) { + for (let i = 0; i < this.#n; i++) { + if (this.#keys[i] === key) { + return this.#values[i]; + } + } + } + + put(key, value) { + for (let i = 0; i < this.#n; i++) { + if (this.#keys[i] === key) { + this.#values[i] = value; + return; + } + } + + this.#keys[this.#n] = key; + this.#values[this.#n] = value; + this.#n++; + } + + delete(key) { + for (let i = 0; i < this.#n; i++) { + if (this.#keys[i] === key) { + for (let j = i; j < this.#n - 1; j++) { + this.#keys[j] = this.#keys[j + 1]; + this.#values[j] = this.#values[j + 1]; + } + this.#n--; + return; + } + } + } + + size() { + return this.#n; + } + + isEmpty() { + return this.#n === 0; + } + + contains(key) { + return !!this.get(key); + } } module.exports = { diff --git a/problem-2/SymbolTable.js b/problem-2/SymbolTable.js index 6bb3c68..59cfff7 100644 --- a/problem-2/SymbolTable.js +++ b/problem-2/SymbolTable.js @@ -71,7 +71,8 @@ class SymbolTable { const mid = start + Math.floor((end - start) / 2); if (key < this.#keys[mid]) { return this.rank(key, start, mid - 1); - } if (key > this.#keys[mid]) { + } + if (key > this.#keys[mid]) { return this.rank(key, mid + 1, end); } return mid; @@ -106,15 +107,54 @@ class SymbolTable { } contains(key) { + return !!this.get(key); } floor(key) { + if (this.isEmpty()) { + return; + } + + const i = this.rank(key); + if (i === 0) { + return this.#keys[i] === key ? key : undefined; + } + + if (this.#keys[i] === key) { + return key; + } + + return this.#keys[i - 1]; } ceiling(key) { + if (this.isEmpty()) { + return; + } + + const i = this.rank(key); + if (i >= this.#n) { + return; + } + + return this.#keys[i]; } keysRange(start, end) { + const startIndex = this.rank(start); + const endIndex = this.rank(end); + + const arr = []; + + for (let i = startIndex; i < endIndex; i++) { + arr.push(this.#keys[i]); + } + + if (this.#keys[endIndex] === end) { + arr.push(end); + } + + return arr; } } diff --git a/problem-3/README.md b/problem-3/README.md index 8a33794..bce459b 100644 --- a/problem-3/README.md +++ b/problem-3/README.md @@ -18,6 +18,8 @@ O: 10 N: 11 ``` +![이진탐색트리그림](./st-image.jpeg) + 2. 트리의 높이를 구하는 height를 구현해 주세요. 이때 2가지 버전의 구현을 만들어 주세요. 첫 번째는 재귀로 실행할 때마다 새로 계산해서 높이를 구하는 버전을 만들어 주세요. 두 번째는 `size()`와 비슷하게 각 노드에 트리의 높이를 담는 diff --git a/problem-3/SymbolTable.js b/problem-3/SymbolTable.js index 8bff19c..131b65c 100644 --- a/problem-3/SymbolTable.js +++ b/problem-3/SymbolTable.js @@ -9,10 +9,13 @@ class Node { n; - constructor(key, value, n) { + height; + + constructor(key, value, n, height = 0) { this.key = key; this.value = value; this.n = n; + this.height = height; } } @@ -46,7 +49,8 @@ class SymbolTable { if (key < node.key) { return this.#get(node.left, key); - } if (key > node.key) { + } + if (key > node.key) { return this.#get(node.right, key); } return node.value; @@ -67,9 +71,13 @@ class SymbolTable { node.right = this.#put(node.right, key, value); } else { node.value = value; + // 이 상황에서는 서브트리의 사이즈가 변하지 않기 때문에 return 처리를 해도 괜찮지 않을까 ? + return node; } node.n = this.#size(node.left) + this.#size(node.right) + 1; + node.height = + 1 + Math.max(this.#height(node.left), this.#height(node.right)); return node; } @@ -143,7 +151,8 @@ class SymbolTable { const t = this.#size(node.left); if (t > k) { return this.#select(node.left, k); - } if (t < k) { + } + if (t < k) { return this.#select(node.right, k - t - 1); } return node; @@ -160,7 +169,8 @@ class SymbolTable { if (key < node.key) { return this.#rank(node.left, key); - } if (key > node.key) { + } + if (key > node.key) { return 1 + this.#size(node.left) + this.#rank(node.right, key); } return this.#size(node.left); @@ -181,6 +191,8 @@ class SymbolTable { node.left = this.#deleteMin(node.left); node.n = this.#size(node.left) + this.#size(node.right) + 1; + node.height = + 1 + Math.max(this.#height(node.left), this.#height(node.right)); return node; } @@ -213,6 +225,8 @@ class SymbolTable { } node.n = this.#size(node.left) + this.#size(node.right) + 1; + node.height = + 1 + Math.max(this.#height(node.left), this.#height(node.right)); return node; } @@ -269,6 +283,18 @@ class SymbolTable { return this.#max(node.right); } + + height() { + return this.#height(this.#root); + } + + #height(node) { + if (node === undefined) { + return -1; + } + + return node.height; + } } module.exports = { diff --git a/problem-3/problem-3.test.js b/problem-3/problem-3.test.js index 9ecdae9..3eec7f0 100644 --- a/problem-3/problem-3.test.js +++ b/problem-3/problem-3.test.js @@ -348,11 +348,12 @@ describe('height', () => { expect(st.height()).toBe(1); st.put('H', 4); + st.put('H', 10); expect(st.height()).toBe(2); - st.put('Z', 5); + st.delete('Z'); - expect(st.height()).toBe(3); + expect(st.height()).toBe(2); }); }); diff --git a/problem-3/st-image.jpeg b/problem-3/st-image.jpeg new file mode 100644 index 0000000..b459dd3 Binary files /dev/null and b/problem-3/st-image.jpeg differ diff --git a/problem-4/README.md b/problem-4/README.md index fa445ed..9661f13 100644 --- a/problem-4/README.md +++ b/problem-4/README.md @@ -8,3 +8,5 @@ E A S Y Q U T I O N 2. 이번에는 위의 키 목록을 레드 블랙 트리에 순서대로 추가했을 때 만들어지는 레드 블랙 트리를 그려주세요. + +![tree](./tree.jpeg) \ No newline at end of file diff --git a/problem-4/tree.jpeg b/problem-4/tree.jpeg new file mode 100644 index 0000000..7917dae Binary files /dev/null and b/problem-4/tree.jpeg differ diff --git a/problem-5/LinearProbingHashTable.jpeg b/problem-5/LinearProbingHashTable.jpeg new file mode 100644 index 0000000..9ad7b50 Binary files /dev/null and b/problem-5/LinearProbingHashTable.jpeg differ diff --git a/problem-5/LinearProbingHashTable.test.js b/problem-5/LinearProbingHashTable.test.js index e4a1b2b..0793529 100644 --- a/problem-5/LinearProbingHashTable.test.js +++ b/problem-5/LinearProbingHashTable.test.js @@ -19,25 +19,106 @@ class LinearProbingHashTable { } hash(key) { - return this.#hash(key) + return this.#hash(key); } get(key) { + for ( + let i = this.#hash(key); + this.#keys[i] !== undefined; + i = (i + 1) % this.#M + ) { + if (this.#keys[i] === key) { + return this.#values[i]; + } + } } put(key, value) { + if (this.#N >= this.#M / 2) { + this.#resize(2 * this.#M); + } + + let i; + for ( + i = this.#hash(key); + this.#keys[i] !== undefined; + i = (i + 1) % this.#M + ) { + if (this.#keys[i] === key) { + this.#values[i] = value; + return; + } + } + + this.#keys[i] = key; + this.#values[i] = value; + this.#N++; } delete(key) { + if (!this.contains(key)) { + return; + } + + let i = this.#hash(key); + while (key !== this.#keys[i]) { + i = (i + 1) % this.#M; + } + + this.#keys[i] = undefined; + this.#values[i] = undefined; + + i = (i + 1) % this.#M; + while (this.#keys[i] !== undefined) { + const keyToRedo = this.#keys[i]; + const valueToRedo = this.#values[i]; + + this.#keys[i] = undefined; + this.#values[i] = undefined; + + this.#N--; + + this.put(keyToRedo, valueToRedo); + + i = (i + 1) % this.#M; + } + + this.#N--; + + if (this.#N > 0 && this.#N === this.#M / 8) { + this.#resize(this.#M / 2); + } } contains(key) { + return this.get(key) !== undefined; } keys() { + const keys = []; + + for (let i = 0; i < this.#M; i++) { + if (this.#keys[i] !== undefined) { + keys.push(this.#keys[i]); + } + } + + return keys; } #resize(capacity) { + const temp = new LinearProbingHashTable(capacity); + + for (let i = 0; i < this.#M; i++) { + if (this.#keys[i] !== undefined) { + temp.put(this.#keys[i], this.#values[i]); + } + } + + this.#keys = temp.#keys; + this.#values = temp.#values; + this.#M = temp.#M; } } @@ -53,7 +134,7 @@ const randomString = (max) => { } return result; -} +}; test('이미 있는 키 값에 값을 추가하면 이전 값을 덮어쓴다', () => { const st = new LinearProbingHashTable(); diff --git a/problem-5/README.md b/problem-5/README.md index 17d1e7f..5709062 100644 --- a/problem-5/README.md +++ b/problem-5/README.md @@ -8,6 +8,8 @@ ``` E A S Y Q U T I O N ``` +![개별 체이닝 해시 테이블](./SeperateChainingHashTable.jpeg) + 2. 선형 탐지 해싱 테이블에 위의 주어진 키를 순서대로 삽입했을 때 테이블의 내부 상태를 그림으로 그려 주세요. 초기 상태는 비어 있고 M=5라고 가정합니다. 삽입할 @@ -15,5 +17,7 @@ E A S Y Q U T I O N 순서를 k라 할 때 해시 함수 11k % M을 이용하여 테이블의 인덱스로 변환해 주세요. +![선형 탐지 해시 테이블](./LinearProbingHashTable.jpeg) + 3. 테스트 요구사항에 맞는 개별 체이닝 해시 테이블과 선형 탐지 해싱 테이블을 구현해 주세요. diff --git a/problem-5/SeperateChainingHashTable.jpeg b/problem-5/SeperateChainingHashTable.jpeg new file mode 100644 index 0000000..c5470ae Binary files /dev/null and b/problem-5/SeperateChainingHashTable.jpeg differ diff --git a/problem-5/SeperateChainingHashTable.test.js b/problem-5/SeperateChainingHashTable.test.js index 6f9e2b1..b0aed5f 100644 --- a/problem-5/SeperateChainingHashTable.test.js +++ b/problem-5/SeperateChainingHashTable.test.js @@ -1,8 +1,125 @@ +class Node { + key; + + item; + + next; + + constructor(key, item, next) { + this.key = key; + this.item = item; + this.next = next; + } +} + +class SymbolTableWithLinkedList { + #first; + + #size; + + constructor() { + this.#size = 0; + } + + get(key) { + for (let i = this.#first; i !== undefined; i = i.next) { + if (i.key === key) { + return i.item; + } + } + } + + put(key, value) { + for (let i = this.#first; i !== undefined; i = i.next) { + if (i.key === key) { + i.item = value; + return; + } + } + + this.#first = new Node(key, value, this.#first); + this.#size++; + } + + delete(key) { + let prev; + + for (let curr = this.#first; curr !== undefined; curr = curr.next) { + if (curr.key === key) { + if (prev) { + prev.next = curr.next; + } else { + this.#first = curr.next; + } + + this.#size--; + return; + } + + prev = curr; + } + } + + contains(key) { + for (let i = this.#first; i !== undefined; i = i.next) { + if (i.key === key) { + return true; + } + } + + return false; + } + + isEmpty() { + return this.#size === 0; + } + + size() { + return this.#size; + } + + keys() { + let current = this.#first; + return { + [Symbol.iterator]() { + return { + next() { + if (current === undefined) { + return { done: true }; + } + + const value = current.key; + + current = current.next; + + return { done: false, value }; + }, + }; + }, + }; + } + + values() { + const array = []; + + for (let i = this.#first; i !== undefined; i = i.next) { + array.push(i.item); + } + + return array; + } +} class SeperateChainingHashTable { #M; + #st; constructor(maxCount = 997) { this.#M = maxCount; + + this.#st = new Array(maxCount); + for (let i = 0; i < maxCount; i++) { + this.#st[i] = new SymbolTableWithLinkedList(); + } } #hash(key) { @@ -14,22 +131,35 @@ class SeperateChainingHashTable { } hash(key) { - return this.#hash(key) + return this.#hash(key); } get(key) { + return this.#st[this.#hash(key)].get(key); } put(key, value) { + this.#st[this.#hash(key)].put(key, value); } delete(key, value) { + this.#st[this.#hash(key)].delete(key); } - + contains(key) { + return this.get(key) !== undefined; } keys() { + const keys = []; + + for (let i = 0; i < this.#M; i++) { + for (const key of this.#st[i].keys()) { + keys.push(key); + } + } + + return keys; } } @@ -45,7 +175,7 @@ const randomString = (max) => { } return result; -} +}; test('이미 있는 키 값에 값을 추가하면 이전 값을 덮어쓴다', () => { const st = new SeperateChainingHashTable();