diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index ec85f6f1..00000000 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,21 +0,0 @@ - -## 요구사항 - -### 기본 -- [x] 기본 항목 1 -- [ ] 기본 항목 2 - -### 심화 -- [ ] 심화 항목 1 -- [ ] 심화 항목 2 - -## 주요 변경사항 -- -- - -## 스크린샷 -![image](이미지url) - -## 멘토에게 -- 셀프 코드 리뷰를 통해 질문 이어가겠습니다. -- diff --git "a/.github/\354\212\244\355\224\204\353\246\260\355\212\270 \353\257\270\354\205\230 12.md" "b/.github/\354\212\244\355\224\204\353\246\260\355\212\270 \353\257\270\354\205\230 12.md" new file mode 100644 index 00000000..b7689468 --- /dev/null +++ "b/.github/\354\212\244\355\224\204\353\246\260\355\212\270 \353\257\270\354\205\230 12.md" @@ -0,0 +1,36 @@ +# 미션목표 + +- 자바스크립트로 정렬 알고리즘 구현하기 + +## 요구사항 + +다음 정렬 알고리즘을 각각 JavaScript 함수로 구현해 주세요. + +선택 정렬 (Selection sort) + +- [x] 숫자형 배열을 파라미터로 받고, 해당 배열을 수정하도록 구현합니다. + +삽입 정렬 (Insertion sort) + +- [x] 숫자형 배열을 파라미터로 받고, 해당 배열을 수정하도록 구현합니다. + +병합 정렬 (Merge sort) + +- [x] 숫자형 배열을 파라미터로 받고, 정렬된 새로운 배열을 리턴하도록 구현합니다. + +퀵 정렬 (Quick sort) + +- [x] 숫자형 배열을 파라미터로 받고, 해당 배열을 수정하도록 구현합니다. + +## 주요 변경사항 + +- +- + +## 스크린샷 + +- ![alt text](image.png) + +## 멘토에게 + +- 셀프 코드 리뷰를 통해 질문 이어가겠습니다. diff --git "a/.github/\354\212\244\355\224\204\353\246\260\355\212\270 \353\257\270\354\205\230 13.md" "b/.github/\354\212\244\355\224\204\353\246\260\355\212\270 \353\257\270\354\205\230 13.md" new file mode 100644 index 00000000..dbdd0988 --- /dev/null +++ "b/.github/\354\212\244\355\224\204\353\246\260\355\212\270 \353\257\270\354\205\230 13.md" @@ -0,0 +1,60 @@ +# 미션 목표 + +- 자바스크립트로 자료 구조 구현하기 +- 자바스크립트로 힙 정렬 구현하기 + +# 요구사항 + +- 다음 자료 구조를 구현해 algorithm 폴더에 저장해 주세요. + +1. 링크드 리스트 (Linked List) + 파일 이름: LinkedList.js + 클래스 이름: LinkedList + 메서드: + addNode(value): 리스트의 끝에 새 노드를 추가 + findNode(value): 주어진 값을 가지는 노드를 찾아 리턴 + insertAfter(targetValue, newValue): 특정 값을 가진 노드 뒤에 새 노드 추가 + removeAfter(targetValue): 특정 값을 가진 노드 뒤의 노드를 삭제 + +2. 이중 링크드 리스트 (Doubly Linked List) + 파일 이름: DoublyLinkedList.js + 클래스 이름: DoublyLinkedList + 메서드: + addToHead(value): 리스트의 앞쪽에 노드 추가 + addToTail(value): 리스트의 뒤쪽에 노드 추가 + insertAfter(targetValue, newValue): 특정 값을 가진 노드 뒤에 새 노드 추가 + findNode(value): 값을 가진 노드를 찾아 반환합니다. + removeNode(value): 특정 값을 가진 노드 삭제 + +3. 큐 (Queue) + 파일 이름: Queue.js + 클래스 이름: Queue + 메서드: + enqueue(value): 큐의 맨 뒤에 값을 추가 + dequeue(): 큐의 앞에서 값을 제거하고 그 값을 리턴 + peek(): 큐의 앞에 있는 값을 제거하지 않고 리턴 + isEmpty(): 큐가 비어 있는지 불린형으로 리턴 + +4. 스택 (Stack) + 파일 이름: Stack.js + 클래스 이름: Stack + 메서드: + push(value): 스택의 맨 위에 값을 추가 + pop(): 스택의 맨 위 값을 제거하고 그 값을 리턴 + peek(): 스택의 맨 위 값을 제거하지 않고 그 값을 리턴 + isEmpty(): 스택이 비어 있는지 불린형으로 리턴 + +5. 이진 탐색 트리 (Binary Search Tree) + 파일 이름: BinarySearchTree.js + 클래스 이름: BinarySearchTree + 메서드: + insert(value): 트리에 값 추가 + find(value): 주어진 값을 찾고 해당 노드를 리턴 + remove(value): 트리에서 해당 값을 삭제 + +- 다음 알고리즘을 JavaScript로 구현해 algorithm + sorts.js 파일에 추가로 작성해 주세요. + +6. 힙 정렬 + 함수 이름: heapsort() + 숫자형 배열을 받아서 받은 배열을 정렬된 상태로 수정 diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..aaefec08 --- /dev/null +++ b/.gitignore @@ -0,0 +1,30 @@ +.DS_Store +.AppleDouble +.LSOverride + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* + +# Dependency directories +node_modules/ + +# Build output +dist/ +build/ + +# Test output +coverage/ + +# Env files +.env +.env.* + +# Editor / OS +.vscode/ +.idea/ + diff --git a/BinarySearchTree.js b/BinarySearchTree.js new file mode 100644 index 00000000..147111bc --- /dev/null +++ b/BinarySearchTree.js @@ -0,0 +1,110 @@ +class TreeNode { + constructor(value) { + this.value = value; + this.left = null; + this.right = null; + } +} + +class BinarySearchTree { + constructor() { + this.root = null; + } + + // 트리에 값 추가 + insert(value) { + const newNode = new TreeNode(value); + if (!this.root) { + this.root = newNode; + return; + } + let current = this.root; + while (true) { + if (value < current.value) { + if (!current.left) { + current.left = newNode; + return; + } + current = current.left; + } else if (value > current.value) { + if (!current.right) { + current.right = newNode; + return; + } + current = current.right; + } else { + return; // 중복값 무시 + } + } + } + + // 주어진 값을 찾고 해당 노드를 리턴 + find(value) { + let current = this.root; + while (current) { + if (value === current.value) return current; + if (value < current.value) { + current = current.left; + } else { + current = current.right; + } + } + return null; + } + + // 트리에서 해당 값을 삭제 + remove(value) { + this.root = this._removeNode(this.root, value); + } + + _removeNode(node, value) { + if (!node) return null; + + if (value < node.value) { + node.left = this._removeNode(node.left, value); + return node; + } else if (value > node.value) { + node.right = this._removeNode(node.right, value); + return node; + } else { + // 1. 자식이 없는 리프 노드인 경우 + if (!node.left && !node.right) return null; + + // 2. 자식이 하나인 경우 + if (!node.left) return node.right; + if (!node.right) return node.left; + + // 3. 자식이 둘인 경우: 우측 서브트리에서 가장 작은 값을 가져옴 + let tempNode = this._getMin(node.right); + node.value = tempNode.value; + node.right = this._removeNode(node.right, tempNode.value); + return node; + } + } + + _getMin(node) { + let current = node; + while (current.left) { + current = current.left; + } + return current; + } +} + +module.exports = BinarySearchTree; + +if (require.main === module) { + const bst = new BinarySearchTree(); + console.log("find (empty):", bst.find(10)); + + [5, 3, 7, 2, 4, 6, 8].forEach((v) => bst.insert(v)); + console.log("find 6:", bst.find(6)?.value); + console.log("find 9:", bst.find(9)); + + bst.remove(2); // leaf + console.log("after remove(2), find 2:", bst.find(2)); + + bst.remove(7); // has two children + console.log("after remove(7), find 7:", bst.find(7)); + console.log("root:", bst.root?.value); +} diff --git a/DoublyLinkedList.js b/DoublyLinkedList.js new file mode 100644 index 00000000..59cb5173 --- /dev/null +++ b/DoublyLinkedList.js @@ -0,0 +1,104 @@ +class DoublyNode { + constructor(value) { + this.value = value; + this.prev = null; + this.next = null; + } +} + +class DoublyLinkedList { + constructor() { + this.head = null; + this.tail = null; + } + + // 리스트의 앞쪽에 노드 추가 + addToHead(value) { + const newNode = new DoublyNode(value); + if (!this.head) { + this.head = newNode; + this.tail = newNode; + } else { + newNode.next = this.head; + this.head.prev = newNode; + this.head = newNode; + } + } + + // 리스트의 뒤쪽에 노드 추가 + addToTail(value) { + const newNode = new DoublyNode(value); + if (!this.tail) { + this.head = newNode; + this.tail = newNode; + } else { + newNode.prev = this.tail; + this.tail.next = newNode; + this.tail = newNode; + } + } + + // 값을 가진 노드를 찾아 반환 + findNode(value) { + let current = this.head; + while (current) { + if (current.value === value) return current; + current = current.next; + } + return null; + } + + // 특정 값을 가진 노드 뒤에 새 노드 추가 + insertAfter(targetValue, newValue) { + const targetNode = this.findNode(targetValue); + if (targetNode) { + const newNode = new DoublyNode(newValue); + newNode.next = targetNode.next; + newNode.prev = targetNode; + + if (targetNode.next) { + targetNode.next.prev = newNode; + } else { + this.tail = newNode; // 마지막 노드였다면 tail 업데이트 + } + targetNode.next = newNode; + } + } + + // 특정 값을 가진 노드 삭제 + removeNode(value) { + const targetNode = this.findNode(value); + if (!targetNode) return; + + if (targetNode.prev) { + targetNode.prev.next = targetNode.next; + } else { + this.head = targetNode.next; // 삭제할 노드가 head인 경우 + } + + if (targetNode.next) { + targetNode.next.prev = targetNode.prev; + } else { + this.tail = targetNode.prev; // 삭제할 노드가 tail인 경우 + } + } +} + +module.exports = DoublyLinkedList; + +if (require.main === module) { + const list = new DoublyLinkedList(); + console.log("find (empty):", list.findNode(1)); + + list.addToHead(2); + list.addToHead(1); + list.addToTail(3); + console.log("head:", list.head?.value, "tail:", list.tail?.value); + + list.insertAfter(2, 2.5); + console.log("after insertAfter(2,2.5):", list.findNode(2.5)?.value); + + list.removeNode(2.5); + console.log("find 2.5 (removed):", list.findNode(2.5)); + console.log("head:", list.head?.value, "tail:", list.tail?.value); +} diff --git a/LinkedList.js b/LinkedList.js new file mode 100644 index 00000000..bd644933 --- /dev/null +++ b/LinkedList.js @@ -0,0 +1,72 @@ +class Node { + constructor(value) { + this.value = value; + this.next = null; + } +} + +class LinkedList { + constructor() { + this.head = null; + } + + // 리스트의 끝에 새 노드를 추가 + addNode(value) { + const newNode = new Node(value); + if (!this.head) { + this.head = newNode; + return; + } + let current = this.head; + while (current.next) { + current = current.next; + } + current.next = newNode; + } + + // 주어진 값을 가지는 노드를 찾아 리턴 + findNode(value) { + let current = this.head; + while (current) { + if (current.value === value) return current; + current = current.next; + } + return null; // 찾지 못한 경우 + } + + // 특정 값을 가진 노드 뒤에 새 노드 추가 + insertAfter(targetValue, newValue) { + const targetNode = this.findNode(targetValue); + if (targetNode) { + const newNode = new Node(newValue); + newNode.next = targetNode.next; + targetNode.next = newNode; + } + } + + // 특정 값을 가진 노드 뒤의 노드를 삭제 + removeAfter(targetValue) { + const targetNode = this.findNode(targetValue); + if (targetNode && targetNode.next) { + targetNode.next = targetNode.next.next; + } + } +} + +module.exports = LinkedList; + +if (require.main === module) { + const list = new LinkedList(); + console.log("find (empty):", list.findNode(1)); + + list.addNode(1); + list.addNode(2); + list.addNode(4); + console.log("find 2:", list.findNode(2)?.value); + + list.insertAfter(2, 3); + console.log("after insertAfter(2,3):", list.findNode(3)?.value); + + list.removeAfter(3); // removes 4 + console.log("find 4 (removed):", list.findNode(4)); +} diff --git a/Queue.js b/Queue.js new file mode 100644 index 00000000..0544b45f --- /dev/null +++ b/Queue.js @@ -0,0 +1,51 @@ +class Queue { + constructor() { + this.items = {}; + this.headIndex = 0; + this.tailIndex = 0; + } + + // 큐의 맨 뒤에 값을 추가 + enqueue(value) { + this.items[this.tailIndex] = value; + this.tailIndex++; + } + + // 큐의 앞에서 값을 제거하고 그 값을 리턴 + dequeue() { + if (this.isEmpty()) return undefined; + const item = this.items[this.headIndex]; + delete this.items[this.headIndex]; + this.headIndex++; + return item; + } + + // 큐의 앞에 있는 값을 제거하지 않고 리턴 + peek() { + if (this.isEmpty()) return undefined; + return this.items[this.headIndex]; + } + + // 큐가 비어 있는지 불린형으로 리턴 + isEmpty() { + return this.headIndex === this.tailIndex; + } +} + +module.exports = Queue; + +if (require.main === module) { + const q = new Queue(); + console.log("isEmpty:", q.isEmpty()); + console.log("peek (empty):", q.peek()); + console.log("dequeue (empty):", q.dequeue()); + + q.enqueue(1); + q.enqueue(2); + q.enqueue(3); + console.log("peek:", q.peek()); + console.log("dequeue:", q.dequeue()); + console.log("dequeue:", q.dequeue()); + console.log("dequeue:", q.dequeue()); + console.log("isEmpty:", q.isEmpty()); +} diff --git a/Stack.js b/Stack.js new file mode 100644 index 00000000..4ce361d1 --- /dev/null +++ b/Stack.js @@ -0,0 +1,43 @@ +class Stack { + constructor() { + this.items = []; + } + + // 스택의 맨 위에 값을 추가 + push(value) { + this.items.push(value); + } + + // 스택의 맨 위 값을 제거하고 그 값을 리턴 + pop() { + return this.items.pop(); + } + + // 스택의 맨 위 값을 제거하지 않고 그 값을 리턴 + peek() { + return this.items[this.items.length - 1]; + } + + // 스택이 비어 있는지 불린형으로 리턴 + isEmpty() { + return this.items.length === 0; + } +} + +module.exports = Stack; + +if (require.main === module) { + const stack = new Stack(); + console.log("isEmpty:", stack.isEmpty()); + console.log("peek (empty):", stack.peek()); + console.log("pop (empty):", stack.pop()); + + stack.push(1); + stack.push(2); + stack.push(3); + console.log("peek:", stack.peek()); + console.log("pop:", stack.pop()); + console.log("pop:", stack.pop()); + console.log("pop:", stack.pop()); + console.log("isEmpty:", stack.isEmpty()); +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..ea96a154 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,12 @@ +{ + "name": "algorithm", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "algorithm", + "version": "1.0.0" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..9acbce7b --- /dev/null +++ b/package.json @@ -0,0 +1,15 @@ +{ + "name": "algorithm", + "version": "1.0.0", + "private": true, + "type": "commonjs", + "scripts": { + "queue": "node algorithm/Queue.js", + "stack": "node algorithm/Stack.js", + "linkedlist": "node algorithm/LinkedList.js", + "doublylinkedlist": "node algorithm/DoublyLinkedList.js", + "bst": "node algorithm/BinarySearchTree.js", + "sorts": "node algorithm/sorts.js", + "test": "node algorithm/Queue.js && node algorithm/Stack.js && node algorithm/LinkedList.js && node algorithm/DoublyLinkedList.js && node algorithm/BinarySearchTree.js" + } +} diff --git a/screenshots/BinarySearchTree.png b/screenshots/BinarySearchTree.png new file mode 100644 index 00000000..92ed2254 Binary files /dev/null and b/screenshots/BinarySearchTree.png differ diff --git a/screenshots/DoublyLinkedList.png b/screenshots/DoublyLinkedList.png new file mode 100644 index 00000000..5de52b0d Binary files /dev/null and b/screenshots/DoublyLinkedList.png differ diff --git a/screenshots/LinkedList.png b/screenshots/LinkedList.png new file mode 100644 index 00000000..21477cff Binary files /dev/null and b/screenshots/LinkedList.png differ diff --git a/screenshots/Queue.png b/screenshots/Queue.png new file mode 100644 index 00000000..92d4b98f Binary files /dev/null and b/screenshots/Queue.png differ diff --git a/screenshots/Stack.png b/screenshots/Stack.png new file mode 100644 index 00000000..4e4f7284 Binary files /dev/null and b/screenshots/Stack.png differ diff --git a/screenshots/sorts.png b/screenshots/sorts.png new file mode 100644 index 00000000..16572d58 Binary files /dev/null and b/screenshots/sorts.png differ diff --git a/sorts.js b/sorts.js new file mode 100644 index 00000000..fc2a2619 --- /dev/null +++ b/sorts.js @@ -0,0 +1,162 @@ +// 선택 정렬 +function selectionSort(nums) { + const n = nums.length; + for (let i = 0; i < n - 1; i += 1) { + let minIndex = i; + for (let j = i + 1; j < n; j += 1) { + if (nums[j] < nums[minIndex]) minIndex = j; + } + if (minIndex !== i) { + const tmp = nums[i]; + nums[i] = nums[minIndex]; + nums[minIndex] = tmp; + } + } + return nums; +} + +// 삽입 정렬 +function insertionSort(nums) { + for (let i = 1; i < nums.length; i += 1) { + const key = nums[i]; + let j = i - 1; + while (j >= 0 && nums[j] > key) { + nums[j + 1] = nums[j]; + j -= 1; + } + nums[j + 1] = key; + } + return nums; +} + +// 병합 정렬 +function mergeSort(nums) { + if (nums.length <= 1) return nums.slice(); + + const mid = Math.floor(nums.length / 2); + const left = mergeSort(nums.slice(0, mid)); + const right = mergeSort(nums.slice(mid)); + + const merged = []; + let i = 0; + let j = 0; + + while (i < left.length && j < right.length) { + if (left[i] <= right[j]) merged.push(left[i++]); + else merged.push(right[j++]); + } + while (i < left.length) merged.push(left[i++]); + while (j < right.length) merged.push(right[j++]); + + return merged; +} + +// 퀵 정렬 +function quickSort(nums) { + function partition(lo, hi) { + const pivot = nums[Math.floor((lo + hi) / 2)]; + let i = lo; + let j = hi; + while (i <= j) { + while (nums[i] < pivot) i += 1; + while (nums[j] > pivot) j -= 1; + if (i <= j) { + const tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + i += 1; + j -= 1; + } + } + return i; + } + + function sort(lo, hi) { + if (lo >= hi) return; + const index = partition(lo, hi); + sort(lo, index - 1); + sort(index, hi); + } + + if (nums.length > 1) sort(0, nums.length - 1); + return nums; +} + +// 힙을 구성하는 헬퍼 함수 +function heapify(arr, length, i) { + let largest = i; + const left = 2 * i + 1; + const right = 2 * i + 2; + + // 왼쪽 자식이 부모보다 크면 최대값을 왼쪽 자식으로 설정 + if (left < length && arr[left] > arr[largest]) { + largest = left; + } + + // 오른쪽 자식이 가장 큰 값보다 크면 최대값을 오른쪽 자식으로 설정 + if (right < length && arr[right] > arr[largest]) { + largest = right; + } + + // 최대값이 루트가 아니라면 교환하고 재귀적으로 힙을 다시 구성 + if (largest !== i) { + const temp = arr[i]; + arr[i] = arr[largest]; + arr[largest] = temp; + + heapify(arr, length, largest); + } +} + +// 힙 정렬 함수 +function heapsort(arr) { + const length = arr.length; + + // 1. 배열을 최대 힙(Max Heap) 구조로 만듭니다. + for (let i = Math.floor(length / 2) - 1; i >= 0; i--) { + heapify(arr, length, i); + } + + // 2. 힙에서 요소를 하나씩 추출하여 배열의 뒤쪽부터 정렬합니다. + for (let i = length - 1; i > 0; i--) { + // 현재 루트(가장 큰 값)를 배열의 끝으로 보냅니다. + const temp = arr[0]; + arr[0] = arr[i]; + arr[i] = temp; + + // 축소된 힙에 대해 다시 최대 힙을 구성합니다. + heapify(arr, i, 0); + } + + return arr; +} + +module.exports = { selectionSort, insertionSort, mergeSort, quickSort, heapsort }; + +if (require.main === module) { + const nums1 = [3, 1, 2]; + console.log("before selectionSort:", nums1); + selectionSort(nums1); + console.log("after selectionSort:", nums1); + + const nums2 = [5, 2, 4, 6, 1, 3]; + console.log("\nbefore insertionSort:", nums2); + insertionSort(nums2); + console.log("after insertionSort:", nums2); + + const nums3 = [3, 1, 2]; + console.log("\nbefore mergeSort:", nums3); + const merged = mergeSort(nums3); + console.log("after mergeSort (new):", merged); + console.log("original stays same :", nums3); + + const nums4 = [3, 1, 2, 2, 0, -1]; + console.log("\nbefore quickSort:", nums4); + quickSort(nums4); + console.log("after quickSort:", nums4); + + const nums5 = [3, 1, 2, 2, 0, -1]; + console.log("\nbefore heapsort:", nums5); + heapsort(nums5); + console.log("after heapsort:", nums5); +}