diff --git a/algorithm/BinarySearchTree.js b/algorithm/BinarySearchTree.js new file mode 100644 index 00000000..67dfbc9a --- /dev/null +++ b/algorithm/BinarySearchTree.js @@ -0,0 +1,174 @@ +class Node { + constructor(data) { + this.data = data; + this.leftChild = null; + this.rightChild = null; + } +} + +class BinarySearchTree { + constructor() { + this.root = null; + } + + insert(value) { + const newNode = new Node(value); + if (this.root === null) { + this.root = newNode; + return; + } + let current = this.root; + while (current) { + if (value < current.data) { + if (current.leftChild === null) { + current.leftChild = newNode; + return; + } + current = current.leftChild; + } else if (value > current.data) { + if (current.rightChild === null) { + current.rightChild = newNode; + return; + } + current = current.rightChild; + } else { + return; + } + } + } + + find(value) { + let current = this.root; + while (current !== null) { + if (value === current.data) return current; + if (value < current.data) { + current = current.leftChild; + } else { + current = current.rightChild; + } + } + return null; + } + + remove(value) { + this.root = this._removeNode(this.root, value); + } + + _removeNode(node, value) { + if (node === null) return null; + + if (value < node.data) { + node.leftChild = this._removeNode(node.leftChild, value); + return node; + } + + if (value > node.data) { + node.rightChild = this._removeNode(node.rightChild, value); + return node; + } + + if (node.leftChild === null && node.rightChild === null) { + return null; + } + + if (node.leftChild === null) return node.rightChild; + if (node.rightChild === null) return node.leftChild; + + let successor = node.rightChild; + while (successor.leftChild !== null) { + successor = successor.leftChild; + } + node.data = successor.data; + node.rightChild = this._removeNode(node.rightChild, successor.data); + return node; + } + + preorder(node = this.root, result = []) { + if (node === null) return result; + result.push(node.data); + this.preorder(node.leftChild, result); + this.preorder(node.rightChild, result); + return result; + } + + inorder(node = this.root, result = []) { + if (node === null) return result; + this.inorder(node.leftChild, result); + result.push(node.data); + this.inorder(node.rightChild, result); + return result; + } + + postorder(node = this.root, result = []) { + if (node === null) return result; + this.postorder(node.leftChild, result); + this.postorder(node.rightChild, result); + result.push(node.data); + return result; + } +} + +const bst = new BinarySearchTree(); + +// 1. insert - 트리에 값 추가 +// +// 50 +// / \ +// 30 70 +// / \ / \ +// 20 40 60 80 +// +bst.insert(50); +bst.insert(30); +bst.insert(70); +bst.insert(20); +bst.insert(40); +bst.insert(60); +bst.insert(80); + +console.log('inorder:', bst.inorder()); // [20, 30, 40, 50, 60, 70, 80] +console.log('preorder:', bst.preorder()); // [50, 30, 20, 40, 70, 60, 80] +console.log('postorder:', bst.postorder()); // [20, 40, 30, 60, 80, 70, 50] + +// 2. find - 값으로 노드 검색 +const found = bst.find(40); +console.log('find 40:', found.data); // find 40: 40 +console.log('find 40 left:', found.leftChild); // find 40 left: null +console.log('find 40 right:', found.rightChild); // find 40 right: null + +const notFound = bst.find(99); +console.log('find 99:', notFound); // find 99: null + +// 3. remove - 리프 노드 삭제 (자식 없음) +// +// 50 +// / \ +// 30 70 +// \ / \ +// 40 60 80 +// +bst.remove(20); +console.log('remove 20:', bst.inorder()); // [30, 40, 50, 60, 70, 80] + +// 4. remove - 자식이 하나인 노드 삭제 +// +// 50 +// / \ +// 40 70 +// / \ +// 60 80 +// +bst.remove(30); +console.log('remove 30:', bst.inorder()); // [40, 50, 60, 70, 80] + +// 5. remove - 자식이 둘인 노드 삭제 (후속자로 대체) +// +// 60 +// / \ +// 40 70 +// \ +// 80 +// +bst.remove(50); +console.log('remove 50:', bst.inorder()); // [40, 60, 70, 80] +console.log('new root:', bst.root.data); // new root: 60 diff --git a/algorithm/DoublyLinkedList.js b/algorithm/DoublyLinkedList.js new file mode 100644 index 00000000..7ebbf5b1 --- /dev/null +++ b/algorithm/DoublyLinkedList.js @@ -0,0 +1,142 @@ +class Node { + constructor(data) { + this.data = data; + this.next = null; + this.prev = null; + } +} + +class DoublyLinkedList { + constructor() { + this.head = null; + this.tail = null; + } + + toString() { + let resStr = '|'; + let iterator = this.head; + while (iterator !== null) { + resStr += ` ${iterator.data} |`; + iterator = iterator.next; + } + return resStr; + } + + findNodeAt(index) { + if (index < 0) return null; + let iterator = this.head; + for (let i = 0; i < index; i++) { + if (iterator === null) return null; + iterator = iterator.next; + } + return iterator; + } + + findNode(value) { + let iterator = this.head; + while (iterator !== null) { + if (iterator.data === value) return iterator; + iterator = iterator.next; + } + return null; + } + + addToTail(value) { + const newNode = new Node(value); + if (this.head === null) { + this.head = newNode; + this.tail = newNode; + } else { + this.tail.next = newNode; + newNode.prev = this.tail; + this.tail = newNode; + } + } + + addToHead(value) { + const newNode = new Node(value); + if (this.head === null) { + this.head = newNode; + this.tail = newNode; + } else { + newNode.next = this.head; + this.head.prev = newNode; + this.head = newNode; + } + } + + insertAfter(targetValue, newValue) { + const prevNode = this.findNode(targetValue); + if (prevNode === null) return; + + const newNode = new Node(newValue); + if (prevNode === this.tail) { + this.tail.next = newNode; + newNode.prev = this.tail; + this.tail = newNode; + } else { + newNode.next = prevNode.next; + newNode.prev = prevNode; + prevNode.next.prev = newNode; + prevNode.next = newNode; + } + } + + removeNode(value) { + const nodeToDelete = this.findNode(value); + if (nodeToDelete === null) return null; + + if (nodeToDelete === this.head && nodeToDelete === this.tail) { + this.head = null; + this.tail = null; + } else if (nodeToDelete === this.head) { + this.head = this.head.next; + if (this.head) this.head.prev = null; + } else if (nodeToDelete === this.tail) { + this.tail = this.tail.prev; + if (this.tail) this.tail.next = null; + } else { + nodeToDelete.prev.next = nodeToDelete.next; + nodeToDelete.next.prev = nodeToDelete.prev; + } + + return nodeToDelete.data; + } +} + +const list = new DoublyLinkedList(); + +// 1. addToTail - 리스트 뒤쪽에 노드 추가 +list.addToTail(10); +list.addToTail(20); +list.addToTail(30); +console.log(list.toString()); // | 10 | 20 | 30 | + +// 2. addToHead - 리스트 앞쪽에 노드 추가 +list.addToHead(5); +list.addToHead(1); +console.log(list.toString()); // | 1 | 5 | 10 | 20 | 30 | + +// 3. findNode - 값으로 노드 검색 +const found = list.findNode(20); +console.log('found:', found.data); // found: 20 + +const notFound = list.findNode(99); +console.log('notFound:', notFound); // notFound: null + +// 4. insertAfter - 특정 값 뒤에 새 노드 삽입 +list.insertAfter(10, 15); +console.log(list.toString()); // | 1 | 5 | 10 | 15 | 20 | 30 | + +list.insertAfter(30, 40); +console.log(list.toString()); // | 1 | 5 | 10 | 15 | 20 | 30 | 40 | + +// 5. removeNode - 특정 값을 가진 노드 삭제 +list.removeNode(1); +console.log(list.toString()); // | 5 | 10 | 15 | 20 | 30 | 40 | + +list.removeNode(15); +console.log(list.toString()); // | 5 | 10 | 20 | 30 | 40 | + +list.removeNode(40); +console.log(list.toString()); // | 5 | 10 | 20 | 30 | diff --git a/algorithm/LinkedList.js b/algorithm/LinkedList.js new file mode 100644 index 00000000..297d3252 --- /dev/null +++ b/algorithm/LinkedList.js @@ -0,0 +1,146 @@ +class Node { + constructor(data) { + this.data = data; + this.next = null; + } +} + +class LinkedList { + constructor() { + this.head = null; + this.tail = null; + this._size = 0; + } + + toString() { + let resStr = '|'; + let iterator = this.head; + while (iterator !== null) { + resStr += ` ${iterator.data} |`; + iterator = iterator.next; + } + return resStr; + } + + findNodeAt(index) { + let iterator = this.head; + for (let i = 0; i < index; i++) { + iterator = iterator.next; + } + return iterator; + } + + findNode(value) { + let iterator = this.head; + while (iterator !== null) { + if (iterator.data === value) { + return iterator; + } + iterator = iterator.next; + } + return null; + } + + addNode(value) { + const newNode = new Node(value); + if (this.head === null) { + this.head = newNode; + this.tail = newNode; + } else { + this.tail.next = newNode; + this.tail = newNode; + } + this._size++; + } + + insertAfter(targetValue, newValue) { + const prevNode = this.findNode(targetValue); + if (prevNode === null) return; + + const newNode = new Node(newValue); + if (prevNode === this.tail) { + this.tail.next = newNode; + this.tail = newNode; + } else { + newNode.next = prevNode.next; + prevNode.next = newNode; + } + this._size++; + } + + removeAfter(targetValue) { + const prevNode = this.findNode(targetValue); + if (prevNode === null || prevNode.next === null) return null; + + const data = prevNode.next.data; + prevNode.next = prevNode.next.next; + if (prevNode.next === null) { + this.tail = prevNode; + } + this._size--; + return data; + } + + prepend(data) { + const newNode = new Node(data); + if (this.head === null) { + this.tail = newNode; + } else { + newNode.next = this.head; + } + this.head = newNode; + this._size++; + } + + popLeft() { + const data = this.head.data; + if (this.head === this.tail) { + this.head = null; + this.tail = null; + } else { + this.head = this.head.next; + } + this._size--; + return data; + } + + getSize() { + return this._size; + } +} + +//예시 +const list = new LinkedList(); + +// 1. addNode - 리스트 끝에 노드 추가 +list.addNode(10); +list.addNode(20); +list.addNode(30); +console.log(list.toString()); // | 10 | 20 | 30 | +console.log('size:', list.getSize()); // size: 3 + +// 2. findNode - 값으로 노드 검색 +const found = list.findNode(20); +console.log('found:', found.data); // found: 20 + +const notFound = list.findNode(99); +console.log('notFound:', notFound); // notFound: null + +// 3. insertAfter - 특정 값 뒤에 새 노드 삽입 +list.insertAfter(20, 25); +console.log(list.toString()); // | 10 | 20 | 25 | 30 | +console.log('size:', list.getSize()); // size: 4 + +list.insertAfter(30, 40); +console.log(list.toString()); // | 10 | 20 | 25 | 30 | 40 | +console.log('size:', list.getSize()); // size: 5 + +// 4. removeAfter - 특정 값 뒤의 노드 삭제 +const removed = list.removeAfter(20); +console.log('removed:', removed); // removed: 25 +console.log(list.toString()); // | 10 | 20 | 30 | 40 | +console.log('size:', list.getSize()); // size: 4 + +list.removeAfter(30); +console.log(list.toString()); // | 10 | 20 | 30 | +console.log('size:', list.getSize()); // size: 3 diff --git a/algorithm/Queue.js b/algorithm/Queue.js new file mode 100644 index 00000000..0f5f4231 --- /dev/null +++ b/algorithm/Queue.js @@ -0,0 +1,98 @@ +class Node { + constructor(data) { + this.data = data; + this.next = null; + } +} + +class Queue { + constructor() { + this.front = null; + this.rear = null; + this._size = 0; + } + + get size() { + return this._size; + } + + isEmpty() { + return this.front === null; + } + + enqueue(value) { + const newNode = new Node(value); + if (this.isEmpty()) { + this.front = newNode; + this.rear = newNode; + } else { + this.rear.next = newNode; + this.rear = newNode; + } + this._size++; + } + + dequeue() { + if (this.isEmpty()) return null; + const data = this.front.data; + if (this.front === this.rear) { + this.front = null; + this.rear = null; + } else { + this.front = this.front.next; + } + this._size--; + return data; + } + + peek() { + return this.isEmpty() ? null : this.front.data; + } + + toString() { + let resStr = 'front => |'; + let iterator = this.front; + while (iterator !== null) { + resStr += ` ${iterator.data} |`; + iterator = iterator.next; + } + return resStr + ' <= rear'; + } +} + +const queue = new Queue(); + +// 1. isEmpty - 빈 큐 확인 +console.log(queue.isEmpty()); // true +console.log('size:', queue.size); // size: 0 + +// 2. enqueue - 큐 뒤쪽에 값 추가 +queue.enqueue(10); +queue.enqueue(20); +queue.enqueue(30); +console.log(queue.toString()); // front => | 10 | 20 | 30 | <= rear +console.log('size:', queue.size); // size: 3 +console.log(queue.isEmpty()); // false + +// 3. peek - 앞쪽 값 확인 (제거 없음) +console.log('peek:', queue.peek()); // peek: 10 +console.log(queue.toString()); // front => | 10 | 20 | 30 | <= rear (변화 없음) + +// 4. dequeue - 앞쪽에서 값 제거 후 리턴 +console.log('dequeue:', queue.dequeue()); // dequeue: 10 +console.log(queue.toString()); // front => | 20 | 30 | <= rear +console.log('size:', queue.size); // size: 2 + +console.log('dequeue:', queue.dequeue()); // dequeue: 20 +console.log(queue.toString()); // front => | 30 | <= rear +console.log('size:', queue.size); // size: 1 + +// 5. 마지막 요소까지 dequeue +console.log('dequeue:', queue.dequeue()); // dequeue: 30 +console.log(queue.toString()); // front => | <= rear +console.log(queue.isEmpty()); // true +console.log('size:', queue.size); // size: 0 + +// 6. 큐에서 dequeue & peek +console.log('dequeue:', queue.dequeue()); // dequeue: null +console.log('peek:', queue.peek()); // peek: null diff --git a/algorithm/Stack.js b/algorithm/Stack.js new file mode 100644 index 00000000..3ab6bb79 --- /dev/null +++ b/algorithm/Stack.js @@ -0,0 +1,87 @@ +class Node { + constructor(data) { + this.data = data; + this.next = null; + } +} + +class Stack { + constructor() { + this.top = null; + this._size = 0; + } + + get size() { + return this._size; + } + + isEmpty() { + return this.top === null; + } + + push(value) { + const newNode = new Node(value); + newNode.next = this.top; + this.top = newNode; + this._size++; + } + + pop() { + if (this.isEmpty()) return null; + const data = this.top.data; + this.top = this.top.next; + this._size--; + return data; + } + + peek() { + return this.isEmpty() ? null : this.top.data; + } + + toString() { + let resStr = 'top => |'; + let iterator = this.top; + while (iterator !== null) { + resStr += ` ${iterator.data} |`; + iterator = iterator.next; + } + return resStr + ' <= bottom'; + } +} + +const stack = new Stack(); + +// 1. isEmpty - 빈 스택 확인 +console.log(stack.isEmpty()); // true +console.log('size:', stack.size); // size: 0 + +// 2. push - 스택 위에 값 추가 +stack.push(10); +stack.push(20); +stack.push(30); +console.log(stack.toString()); // top => | 30 | 20 | 10 | <= bottom +console.log('size:', stack.size); // size: 3 +console.log(stack.isEmpty()); // false + +// 3. peek - 맨 위 값 확인 (제거 없음) +console.log('peek:', stack.peek()); // peek: 30 +console.log(stack.toString()); // top => | 30 | 20 | 10 | <= bottom (변화 없음) + +// 4. pop - 맨 위 값 제거 후 리턴 +console.log('pop:', stack.pop()); // pop: 30 +console.log(stack.toString()); // top => | 20 | 10 | <= bottom +console.log('size:', stack.size); // size: 2 + +console.log('pop:', stack.pop()); // pop: 20 +console.log(stack.toString()); // top => | 10 | <= bottom +console.log('size:', stack.size); // size: 1 + +// 5. 마지막 요소까지 pop +console.log('pop:', stack.pop()); // pop: 10 +console.log(stack.toString()); // top => | <= bottom +console.log(stack.isEmpty()); // true +console.log('size:', stack.size); // size: 0 + +// 6. 빈 스택에서 pop & peek +console.log('pop:', stack.pop()); // pop: null +console.log('peek:', stack.peek()); // peek: null diff --git a/algorithm/sorts.js b/algorithm/sorts.js new file mode 100644 index 00000000..e4d9a83f --- /dev/null +++ b/algorithm/sorts.js @@ -0,0 +1,224 @@ +// 테스트용 배열 +const testArray = [5, 3, 8, 4, 2]; + +// 선택정렬 (Selection Sort) + +function selectionSort(arr) { + console.log('\n=== Selection Sort 시작 ==='); + const a = [...arr]; + + for (let i = 0; i < a.length - 1; i++) { + let minIndex = i; + console.log(`\n[i=${i}] 현재 배열:`, a); + + for (let j = i + 1; j < a.length; j++) { + console.log(` j=${j} 비교: ${a[j]} < ${a[minIndex]}`); + if (a[j] < a[minIndex]) { + minIndex = j; + } + } + + if (minIndex !== i) { + console.log(` swap: ${a[i]} ↔ ${a[minIndex]}`); + [a[i], a[minIndex]] = [a[minIndex], a[i]]; + } + } + + console.log('결과:', a); + return a; +} + +// 삽입정렬 (Insertion Sort) +function insertionSort(arr) { + console.log('\n=== Insertion Sort 시작 ==='); + const a = [...arr]; + + for (let i = 1; i < a.length; i++) { + let current = a[i]; + let j = i - 1; + + console.log(`\n[i=${i}] current=${current}, 배열:`, a); + + while (j >= 0 && a[j] > current) { + console.log(` ${a[j]} > ${current} → 이동`); + a[j + 1] = a[j]; + j--; + } + + a[j + 1] = current; + console.log(' 삽입 후:', a); + } + + console.log('결과:', a); + return a; +} + +// 병합정렬 (Merge Sort) +function mergeSort(arr) { + console.log('mergeSort 호출:', arr); + + if (arr.length <= 1) return arr; + + const mid = Math.floor(arr.length / 2); + const left = arr.slice(0, mid); + const right = arr.slice(mid); + + const sortedLeft = mergeSort(left); + const sortedRight = mergeSort(right); + + const merged = merge(sortedLeft, sortedRight); + console.log('merge 결과:', merged); + + return merged; +} + +function merge(left, right) { + console.log(` merge(${left}, ${right})`); + + const result = []; + let i = 0; + let j = 0; // 🔥 수정된 부분 + + while (i < left.length && j < right.length) { + if (left[i] <= right[j]) { + result.push(left[i++]); + } else { + result.push(right[j++]); + } + } + + result.push(...left.slice(i)); + result.push(...right.slice(j)); + + return result; +} + +// 퀵정렬 (Quick Sort) +function quickSort(arr, start = 0, end = arr.length - 1) { + if (start === 0 && end === arr.length - 1) { + console.log('\n=== Quick Sort 시작 ==='); + } + + if (start < end) { + const pivotIndex = partition(arr, start, end); + console.log(`pivot 위치: ${pivotIndex}, 배열:`, arr); + + quickSort(arr, start, pivotIndex - 1); + quickSort(arr, pivotIndex + 1, end); + } + + return arr; +} + +function partition(arr, start, end) { + const pivot = arr[end]; + console.log(`\npartition: pivot=${pivot}, 범위=[${start},${end}]`); + + let i = start - 1; + + for (let j = start; j < end; j++) { + console.log(` 비교: ${arr[j]} <= ${pivot}`); + if (arr[j] <= pivot) { + i++; + [arr[i], arr[j]] = [arr[j], arr[i]]; + console.log(' swap:', arr); + } + } + + [arr[i + 1], arr[end]] = [arr[end], arr[i + 1]]; + console.log(' pivot swap:', arr); + + return i + 1; +} + +console.log('\n원본 배열:', testArray); + +selectionSort(testArray); +insertionSort(testArray); +console.log('\n=== Merge Sort 시작 ==='); +console.log('결과:', mergeSort([...testArray])); + +quickSort([...testArray]); + +function swap(tree, index1, index2) { + const temp = tree[index1]; + tree[index1] = tree[index2]; + tree[index2] = temp; +} + +function heapify(tree, index, treeSize) { + const leftChild = 2 * index + 1; + const rightChild = 2 * index + 2; + let largest = index; + + if (leftChild < treeSize && tree[largest] < tree[leftChild]) { + largest = leftChild; + } + + if (rightChild < treeSize && tree[largest] < tree[rightChild]) { + largest = rightChild; + } + + if (largest !== index) { + swap(tree, index, largest); + heapify(tree, largest, treeSize); + } +} + +function heapsort(tree) { + const treeSize = tree.length; + + for (let i = Math.floor(treeSize / 2) - 1; i >= 0; i--) { + heapify(tree, i, treeSize); + } + + for (let i = treeSize - 1; i > 0; i--) { + swap(tree, 0, i); + heapify(tree, 0, i); + } +} + +//힙 정렬 예시 + +// 1. 기본 정렬 +const arr1 = [6, 1, 8, 3, 5, 2, 7, 4]; +console.log('before:', [...arr1]); // before: [6, 1, 8, 3, 5, 2, 7, 4] +heapsort(arr1); +console.log('after:', arr1); // after: [1, 2, 3, 4, 5, 6, 7, 8] + +// 2. 이미 정렬된 배열 +const arr2 = [1, 2, 3, 4, 5]; +heapsort(arr2); +console.log('sorted:', arr2); // sorted: [1, 2, 3, 4, 5] + +// 3. 역순 배열 +const arr3 = [5, 4, 3, 2, 1]; +heapsort(arr3); +console.log('reverse:', arr3); // reverse: [1, 2, 3, 4, 5] + +// 4. 중복 값이 있는 배열 +const arr4 = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3]; +heapsort(arr4); +console.log('dupes:', arr4); // dupes: [1, 1, 2, 3, 3, 4, 5, 5, 6, 9] + +// 5. 요소가 하나인 배열 +const arr5 = [42]; +heapsort(arr5); +console.log('single:', arr5); // single: [42] + +// 6. 동작 과정 시각화 (arr = [6, 1, 8, 3]) +// +// 원본 배열: heap 구성 후: 정렬 과정: +// +// 6 8 swap(8,3) → heapify +// / \ / \ 3 +// 1 8 6 6 / \ +// / / 6 6 +// 3 3 / +// 8 ← 정렬 완료 영역 +// +// 최종 결과: [1, 3, 6, 8] +// +const arr6 = [6, 1, 8, 3]; +heapsort(arr6); +console.log('visual:', arr6); // visual: [1, 3, 6, 8]