├── .gitignore ├── .babelrc ├── advanced ├── graph │ ├── travelling_salesman_problem │ │ ├── readme.md │ │ ├── TSP.spec.js │ │ └── TSP.js │ ├── single_source_shortest_path │ │ ├── image_1.png │ │ ├── Dijkstra.spec.js │ │ ├── Dijkstra.js │ │ └── readme.md │ ├── readme.md │ ├── all_pairs_shortest_path │ │ ├── Floyd.spec.js │ │ └── Floyd.js │ └── minimum_spanning_tree │ │ ├── MST.spec.js │ │ └── MST.js ├── Fibonacci.js ├── LCS.js └── Knapsack.js ├── data_structures ├── queue │ ├── Queue.spec.txt │ ├── PriorityQueue │ │ ├── BasicPriorityQueue.spec.js │ │ ├── BinaryHeapPriorityQueue.spec.js │ │ ├── BasicPriorityQueue.js │ │ └── BinaryHeapPriorityQueue.js │ ├── Queue.js │ ├── readme.md │ └── Queue.spec.js ├── linkedList │ ├── SimpleLinkedList.spec.js │ ├── DoublySimpleLinkedList.spec.js │ ├── DoublyLinkedList.js │ ├── DoublyLinkedList.spec.js │ ├── DoublySimpleLinkedList.js │ ├── SimpleLinkedList.js │ ├── LinkedList.js │ ├── LinkedList.spec.js │ └── readme.md ├── stack │ ├── Stack.js │ ├── Stack.spec.js │ └── readme.md ├── binarySearchTree │ ├── BST.spec.js │ ├── BinarySearchTree.js │ ├── BinarySearchTree.spec.js │ ├── BST.js │ └── readme.md └── graph │ ├── Graph.spec.js │ ├── Graph.js │ └── readme.md ├── sort ├── insertionSort │ ├── InsertionSort.spec.js │ └── InsertionSort.js ├── selectionSort │ ├── SelectionSort.spec.js │ └── SelectionSort.js ├── bubbleSort │ ├── BubbleSort.spec.js │ ├── BubbleSort.js │ ├── readme.md │ └── CompareRuntime.spec.js ├── readme.md ├── mergeSort │ ├── MergeSort.spec.js │ └── MergeSort.js ├── quickSort │ ├── QuickSort.spec.js │ └── QuickSort.js ├── shellSort │ ├── ShellSort.spec.js │ └── ShellSort.js ├── RandomArray.js └── RandomArray.spec.js ├── package.json ├── spec └── advanced │ ├── Fibonacci.spec.js │ ├── LCS.spec.js │ └── Knapsack.spec.js ├── search ├── binarySearch │ ├── BinarySearch.spec.js │ └── BinarySearch.js ├── CompareTextSearch.spec.js └── linearSearch │ ├── LinearSearch.spec.js │ └── LinearSearch.js ├── readme.md └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"] 3 | } -------------------------------------------------------------------------------- /advanced/graph/travelling_salesman_problem/readme.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /advanced/graph/single_source_shortest_path/image_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ckboyjiy/javascript_ds_algorithms/HEAD/advanced/graph/single_source_shortest_path/image_1.png -------------------------------------------------------------------------------- /data_structures/queue/Queue.spec.txt: -------------------------------------------------------------------------------- 1 | F Allison McMillan 2 | M Frank Opitz 3 | M Mason McMillan 4 | M Clayton Ruff 5 | F Cheryl Ferenback 6 | M Raymond Williams 7 | F Jennifer Ingram 8 | M Bryan Frazer 9 | M David Durr 10 | M Danny Martin 11 | F Aurora Adney -------------------------------------------------------------------------------- /sort/insertionSort/InsertionSort.spec.js: -------------------------------------------------------------------------------- 1 | import {expect} from 'chai'; 2 | import {InsertionSort} from "./InsertionSort"; 3 | 4 | describe('Insertion class test', ()=> { 5 | it('should be sort array', () => { 6 | let arr = new InsertionSort(); 7 | arr.setData(10); 8 | arr.insertionSort(); 9 | 10 | }) 11 | }) -------------------------------------------------------------------------------- /sort/selectionSort/SelectionSort.spec.js: -------------------------------------------------------------------------------- 1 | import {expect} from 'chai'; 2 | import {SelectionSort} from "./SelectionSort"; 3 | 4 | describe('SelectionSort class test', () => { 5 | let arr; 6 | it('should be sort array', ()=> { 7 | arr = new SelectionSort(); 8 | arr.setData(10); 9 | arr.selectionSort(); 10 | expect(arr.getArray().filter((val,idx, arr)=> val > arr[idx+1]).length).to.equal(0); 11 | }) 12 | }) -------------------------------------------------------------------------------- /sort/bubbleSort/BubbleSort.spec.js: -------------------------------------------------------------------------------- 1 | import {expect} from 'chai'; 2 | import {BubbleSort} from "./BubbleSort"; 3 | 4 | describe('BubbleSort class test', ()=> { 5 | let arr; 6 | beforeEach(()=> { 7 | arr = new BubbleSort(); 8 | }); 9 | it('should be sort array', () => { 10 | arr.setData(10); 11 | arr.bubbleSort(); 12 | expect(arr.getArray().filter((val,idx, arr)=> val > arr[idx+1]).length).to.equal(0); 13 | }) 14 | }) -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "javascript_datastructures", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "./node_modules/.bin/mocha --require babel-register **/*[sS]pec.js" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "devDependencies": { 12 | "babel-core": "^6.26.0", 13 | "babel-polyfill": "^6.26.0", 14 | "babel-preset-es2015": "^6.24.1", 15 | "babel-register": "^6.26.0", 16 | "chai": "^4.1.2", 17 | "mocha": "^5.1.1" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /sort/readme.md: -------------------------------------------------------------------------------- 1 | # 정렬 2 | 숫자들을 정렬하는 기본적인 정렬 알고리즘에 대해서 알아보려고 합니다. 3 | 정렬을 구현해보기 전에 보다 편하게 정렬을 구현해보기 위해 임의의 숫자를 발생시키는 배열 객체를 준비하려고 합니다. 4 | 5 | 이 객체는 지정한 숫자만큼의 중복가능한 임의의 숫자를 발생시키는 메서드와 6 | 배열을 초기화하고, 배열에 지정된 값을 추가하거나, 두 배열의 값을 교체하고, 배열을 초기화하거나 배열의 항목을 받는 다양한 메서드를 구현한 클래스입니다. 7 | 8 | [RandomArray.js](./RandomArray.js) 9 | 10 | 우리는 이 클래스를 상속받아서 다양한 정렬 클래스를 구현해보도록 하겠습니다. 11 | 12 | 다양한 정렬 클래스는 아래와 같이 상속을 구현합니다. 13 | ```javascript 14 | class SomeeSort extends RandomArray { 15 | constructor(number = 0) { 16 | super(number); 17 | } 18 | } 19 | ``` -------------------------------------------------------------------------------- /sort/mergeSort/MergeSort.spec.js: -------------------------------------------------------------------------------- 1 | import {expect} from 'chai'; 2 | import {MergeSort} from "./MergeSort"; 3 | 4 | describe('zz', () => { 5 | it('aa', () => { 6 | let arr = new MergeSort(); 7 | arr.setData(10000); 8 | let start = new Date().getTime(); 9 | arr.mergeSort(); 10 | let stop = new Date().getTime(); 11 | let elapsed = stop - start; 12 | console.log(`The elapsed time was ${elapsed} milliseconds`); 13 | expect(arr.getArray().filter((val,idx, arr)=> val > arr[idx+1]).length).to.equal(0); 14 | }) 15 | }) -------------------------------------------------------------------------------- /sort/quickSort/QuickSort.spec.js: -------------------------------------------------------------------------------- 1 | import {expect} from 'chai'; 2 | import {QuickSort} from "./QuickSort"; 3 | describe('QuickSort class test', () => { 4 | it('should be sort a array', ()=> { 5 | const arr = new QuickSort(); 6 | arr.setData(10000000); 7 | let start = new Date().getTime(); 8 | arr.quickSort(); 9 | let stop = new Date().getTime(); 10 | let elapsed = stop - start; 11 | console.log(`The elapsed time was ${elapsed} milliseconds`); 12 | expect(arr.getArray().filter((val,idx, arr)=> val > arr[idx+1]).length).to.equal(0); 13 | }) 14 | }) -------------------------------------------------------------------------------- /advanced/graph/readme.md: -------------------------------------------------------------------------------- 1 | # 그래프 관련 알고리즘 2 | 3 | ## 최단경로문제 (Shortest Path Problem) 4 | 가중치를 가진 간선으로 이뤄진 그래프에서 정점 u에서 정점 v까지의 경로 중 경로의 값이 가장 작은 경로를 찾는 문제입니다. 5 | 정점 u는 시작점(source)라고 말하고, 정점 v는 도착점(destination)이라고 합니다. 6 | 7 | 최단경로문제는 아래와 같은 유형으로 분류할 수 있습니다. 8 | * Single-Source & Single-Destination : 시작점과 도착점이 각각 주어진 상황에서 최단 경로를 푸는 문제 9 | * Single-Source : 시작점만 주어진 상황에서 나머지 모든 정점까지의 최단 경로를 푸는 문제 10 | * Single-destination : 도착점만 주어진 상황에서 나머지 모든 정점을 시작점으로 최단 경로를 푸는 문제 11 | * All pairs : 그래프의 모든 정점을 각각 하나의 쌍으로 구성하여 그 쌍들의 최단 경로를 푸는 문제 12 | 13 | ## Single-Source & Single-Destination 14 | 15 | ## Single-Source 16 | * [다익스트라 알고리즘](./single_source_shortest_path) 17 | 18 | ## Single-destination 19 | 20 | ## All pairs 21 | -------------------------------------------------------------------------------- /sort/bubbleSort/BubbleSort.js: -------------------------------------------------------------------------------- 1 | import {RandomArray} from "../RandomArray"; 2 | 3 | export class BubbleSort extends RandomArray { 4 | constructor(number = 0) { 5 | super(number); 6 | } 7 | bubbleSort() { 8 | // 외부 반복문은 배열에서 정렬이 확인되지 않은 배열의 위치를 통제합니다. (배열의 가장 오른쪽 값이 가장 크다고 확인된 인덱스는 제외하는 작업) 9 | for(let outer = this.dataStore.length-1; outer >= 1; outer--) { 10 | // 내부 반복문은 배열의 처음부터 정렬 확인이 안된 배열의 위치까지 11 | for(let inner = 0; inner < outer; inner++) { 12 | if (this.dataStore[inner] > this.dataStore[inner+1]) { // 인접한 값을 비교하여 왼쪽 값이 오른쪽 값보다 더 크다면 13 | this.swap(inner, inner+1); // 두 값을 교환합니다. 14 | } 15 | } 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /sort/selectionSort/SelectionSort.js: -------------------------------------------------------------------------------- 1 | import {RandomArray} from "../RandomArray"; 2 | 3 | export class SelectionSort extends RandomArray { 4 | constructor(number = 0) { 5 | super(number); 6 | } 7 | selectionSort() { 8 | // console.log(this.toString()); 9 | for (let outer = 0; outer < this.dataStore.length; outer++) { 10 | let minIndex = outer; 11 | for (let inner = outer+1; inner < this.dataStore.length; inner++) { 12 | if (this.dataStore[inner] < this.dataStore[minIndex]) { 13 | minIndex = inner; 14 | } 15 | } 16 | this.swap(outer, minIndex); 17 | // console.log(this.toString()); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /sort/shellSort/ShellSort.spec.js: -------------------------------------------------------------------------------- 1 | import {expect} from 'chai'; 2 | import {ShellSort} from "./ShellSort"; 3 | 4 | describe('ShellSort class test', ()=> { 5 | it('should be sort a array', ()=> { 6 | const arr = new ShellSort(); 7 | arr.setData(100000); 8 | let start = new Date().getTime(); 9 | arr.shellSort(); 10 | let stop = new Date().getTime(); 11 | let elapsed = stop - start; 12 | console.log(`The elapsed time was ${elapsed} milliseconds`); 13 | 14 | arr.clear(); 15 | arr.setData(100000); 16 | start = new Date().getTime(); 17 | arr.dynamicShellSort(); 18 | stop = new Date().getTime(); 19 | elapsed = stop - start; 20 | console.log(`The elapsed time was ${elapsed} milliseconds`); 21 | }) 22 | }) -------------------------------------------------------------------------------- /advanced/graph/all_pairs_shortest_path/Floyd.spec.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { Floyd } from "./Floyd"; 3 | 4 | describe('Floyd-Warshall Algorithm test', () => { 5 | it('1', () => { 6 | const graph = new Floyd(); 7 | graph.addVertex('A'); 8 | graph.addVertex('B'); 9 | graph.addVertex('C'); 10 | graph.addVertex('D'); 11 | graph.addVertex('S') 12 | 13 | graph.addEdge('A', 'B', 2); 14 | graph.addEdge('A', 'C', 5); 15 | graph.addEdge('A', 'D', 1); 16 | 17 | graph.addEdge('B', 'C', 3); 18 | graph.addEdge('B', 'D', 6); 19 | 20 | graph.addEdge('C', 'D', 4); 21 | 22 | graph.addEdge('S', 'D', 8); 23 | graph.addEdge('S', 'C', 7); 24 | 25 | graph.search(); 26 | console.log('done'); 27 | }); 28 | }) -------------------------------------------------------------------------------- /sort/insertionSort/InsertionSort.js: -------------------------------------------------------------------------------- 1 | import {RandomArray} from "../RandomArray"; 2 | 3 | export class InsertionSort extends RandomArray { 4 | constructor(number = 0) { 5 | super(number); 6 | } 7 | insertionSort() { 8 | let temp, inner; 9 | // console.log(this.toString() + "\n"); 10 | for(let outer = 1; outer <= this.dataStore.length-1; outer++) { 11 | temp = this.dataStore[outer]; 12 | inner = outer; 13 | while(inner > 0 && (this.dataStore[inner-1] >= temp) ) { 14 | this.dataStore[inner] = this.dataStore[inner-1]; 15 | // console.log(this.toString()); 16 | --inner; 17 | } 18 | this.dataStore[inner] = temp; 19 | // console.log(this.toString() + "\n"); 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /advanced/graph/minimum_spanning_tree/MST.spec.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { MST } from "./MST"; 3 | 4 | describe('MST for Prim\'s algorithm Test', () => { 5 | it('case 1', () => { 6 | const graph = new MST(); 7 | 8 | graph.addVertex('A'); 9 | graph.addVertex('B'); 10 | graph.addVertex('C'); 11 | graph.addVertex('D'); 12 | graph.addVertex('S') 13 | 14 | graph.addEdge('A', 'B', 2); 15 | graph.addEdge('A', 'C', 5); 16 | graph.addEdge('A', 'D', 1); 17 | 18 | graph.addEdge('B', 'C', 3); 19 | graph.addEdge('B', 'D', 6); 20 | 21 | graph.addEdge('C', 'D', 4); 22 | 23 | graph.addEdge('S', 'D', 8); 24 | graph.addEdge('S', 'C', 7); 25 | 26 | expect(graph.prim().join(', ')).to.equal('A, D, B, C, S'); 27 | }) 28 | }) -------------------------------------------------------------------------------- /data_structures/queue/PriorityQueue/BasicPriorityQueue.spec.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { BasicPriorityQueue } from "./BasicPriorityQueue"; 3 | 4 | describe('BasicPriorityQueue Test', () => { 5 | it('case 1 : ascending queue', () => { 6 | const queue = new BasicPriorityQueue(); 7 | queue.enqueue('A', 100); 8 | queue.enqueue('B', 19); 9 | queue.enqueue('C', 36); 10 | queue.enqueue('D', 17); 11 | queue.enqueue('E', 3); 12 | queue.enqueue('F', 25); 13 | queue.enqueue('G', 1); 14 | queue.enqueue('H', 2); 15 | queue.enqueue('I', 7); 16 | 17 | const result = []; 18 | while(!queue.isEmpty()) { 19 | result.push(queue.dequeue().priority); 20 | } 21 | expect(result.join(', ')).to.equal('1, 2, 3, 7, 17, 19, 25, 36, 100'); 22 | }); 23 | }) -------------------------------------------------------------------------------- /advanced/graph/single_source_shortest_path/Dijkstra.spec.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { Dijkstra } from './Dijkstra'; 3 | 4 | describe(`Dijkstra's Algorithm test`, () => { 5 | 6 | it('1', () => { 7 | const graph = new Dijkstra(); 8 | graph.addVertex('A'); 9 | graph.addVertex('B'); 10 | graph.addVertex('C'); 11 | graph.addVertex('D'); 12 | graph.addVertex('S') 13 | 14 | graph.addEdge('A', 'B', 2); 15 | graph.addEdge('A', 'C', 5); 16 | graph.addEdge('A', 'D', 1); 17 | 18 | graph.addEdge('B', 'C', 3); 19 | graph.addEdge('B', 'D', 6); 20 | 21 | graph.addEdge('C', 'D', 4); 22 | 23 | graph.addEdge('S', 'D', 8); 24 | graph.addEdge('S', 'C', 7); 25 | 26 | const shortest = graph.shortest('S'); 27 | const result = shortest.filter(val => val.distance > 0).reduce((prev, curr) => prev.distance < curr.distance ? prev : curr); 28 | expect(result.distance).to.equal(7); 29 | }) 30 | }) -------------------------------------------------------------------------------- /spec/advanced/Fibonacci.spec.js: -------------------------------------------------------------------------------- 1 | import {expect} from 'chai'; 2 | import {Fibonacci} from "../../advanced/Fibonacci"; 3 | describe('The dynamic programming test', ()=> { 4 | it('should be compute the fibonacci numbers', ()=> { 5 | const n = 10; 6 | let fibo = new Fibonacci(); 7 | let start = 0; 8 | let stop = 0; 9 | 10 | start = new Date().getTime(); 11 | console.log(fibo.iter(n)); 12 | stop = new Date().getTime(); 13 | console.log(`iterative time - ${stop-start} milliseconds`); 14 | 15 | start = new Date().getTime(); 16 | console.log(fibo.dynamic(n)); 17 | stop = new Date().getTime(); 18 | console.log(`dynamic programming time - ${stop-start} milliseconds`); 19 | 20 | start = new Date().getTime(); 21 | console.log(fibo.recursion(n)); 22 | stop = new Date().getTime(); 23 | console.log(`recursive time - ${stop-start} milliseconds`); 24 | }); 25 | it('lcs test', ()=> { 26 | let prog = new Fibonacci(); 27 | console.log(prog.lcs('AGGTAB', 'GXTXAYB')); 28 | }) 29 | }) -------------------------------------------------------------------------------- /data_structures/linkedList/SimpleLinkedList.spec.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { SimpleLinkedList } from './SimpleLinkedList'; 3 | 4 | describe('SimpleLinkedList test', () => { 5 | 6 | let list; 7 | beforeEach( () => { 8 | list = new SimpleLinkedList(); 9 | }); 10 | 11 | it('should be create SimpleLinkedList class', () => { 12 | expect(list instanceof SimpleLinkedList).to.be.true; 13 | }); 14 | 15 | it('should be find a node by index', () => { 16 | expect(list.find(0)).to.be.null; 17 | }); 18 | 19 | it('should be add a node', () => { 20 | list.add(1); 21 | expect(list.length).to.equal(1); 22 | list.add(2); 23 | expect(list.length).to.equal(2); 24 | expect(list.find(1).element).to.equal(2); 25 | }); 26 | 27 | it('should be remove a node at specified index', () => { 28 | list.add(1); 29 | list.add(2); 30 | list.add(5); 31 | list.add(3); 32 | expect(list.find(2).element).to.equal(5); 33 | list.remove(2); 34 | expect(list.find(2).element).to.equal(3); 35 | }); 36 | }) -------------------------------------------------------------------------------- /data_structures/linkedList/DoublySimpleLinkedList.spec.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { DoublySimpleLinkedList} from "./DoublySimpleLinkedList"; 3 | 4 | describe('DoublySimpleLinkedList test', () => { 5 | let list; 6 | beforeEach(()=> { 7 | list = new DoublySimpleLinkedList(); 8 | }); 9 | 10 | it('should be create DoublySimpleLinkedList class', () => { 11 | expect(list instanceof DoublySimpleLinkedList).to.be.true; 12 | }); 13 | it('should be find a node by index', () => { 14 | expect(list.find(0)).to.be.null; 15 | }); 16 | it('should be add a node', () => { 17 | list.add(1); 18 | expect(list.length).to.equal(1); 19 | list.add(2); 20 | expect(list.length).to.equal(2); 21 | expect(list.find(1).element).to.equal(2); 22 | }); 23 | it('should be remove a node at specified index', () => { 24 | list.add(1); 25 | list.add(2); 26 | list.add(5); 27 | list.add(3); 28 | expect(list.find(2).element).to.equal(5); 29 | list.remove(2); 30 | expect(list.find(2).element).to.equal(3); 31 | }); 32 | }); -------------------------------------------------------------------------------- /search/binarySearch/BinarySearch.spec.js: -------------------------------------------------------------------------------- 1 | import {expect} from 'chai'; 2 | import {BinarySearch} from "./BinarySearch"; 3 | 4 | describe('BinarySearch class test', ()=> { 5 | it('should be retrieved index number of equal a specified value', ()=> { 6 | const arr = new BinarySearch(); 7 | arr.setData(1000000); 8 | arr.quickSort(); 9 | let start = new Date().getTime(); 10 | expect(arr.search(1000001)).to.equal(arr.getArray().findIndex( val => val === 1000001)); 11 | let stop = new Date().getTime(); 12 | let elapsed = stop - start; 13 | console.log(`The elapsed time was ${elapsed} milliseconds`); 14 | }); 15 | it('should be get count of value which equal a specified value', () => { 16 | const arr = new BinarySearch(); 17 | arr.setData(1000000); 18 | arr.quickSort(); 19 | let start = new Date().getTime(); 20 | expect(arr.count(500)).to.equal(arr.getArray().filter( val => val === 500).length); 21 | let stop = new Date().getTime(); 22 | let elapsed = stop - start; 23 | console.log(`The elapsed time was ${elapsed} milliseconds`); 24 | }) 25 | }) -------------------------------------------------------------------------------- /sort/bubbleSort/readme.md: -------------------------------------------------------------------------------- 1 | # 버블 정렬 2 | 버블 정렬 알고리즘은 가장 느린 정렬 알고리즘 가운데 하나지만 그만큼 구현하기는 가장 쉽습니다. 3 | 4 | 버블정렬은 데이터을 정렬할 때 배열의 한쪽 끝에서 다른 쪽 끝으로 버블처럼 값이 떠오른다는 의미에서 유래되었습니다. 5 | 데이터 집합이 오른차순으로 정렬된다면 큰 값은 배열의 오른쪽으로 작은 값은 배열의 왼쪽으로 이동해야 합니다. 6 | 따라서 데이터를 여러번 반복적으로 탐색하면서 인접한 값을 서로 비교해 왼쪽 값이 오른쪽 값보다 크다면 두 값을 서로 바꿔야 합니다. 7 | 8 | 설명보단 아래의 gif 파일 하나가 더 효과적일 듯 합니다. 9 | 10 | ![버블정렬](https://upload.wikimedia.org/wikipedia/commons/0/06/Bubble-sort.gif) 11 | [출처 : https://commons.wikimedia.org] 12 | 13 | 결론적으로 버블정렬은 항목 중 가장 큰 값을 하나씩 배열의 가장 오른쪽으로 위치시키는 작업을 반복합니다. 14 | 이제 코드로 구현해보겠습니다. 15 | ```javascript 16 | class BubbleSort extends RandomArray { 17 | ... 18 | bubbleSort() { 19 | // 외부 반복문은 배열에서 정렬이 확인되지 않은 배열의 위치를 통제합니다. (배열의 가장 오른쪽 값이 가장 크다고 확인된 인덱스는 제외하는 작업) 20 | for(let outer = this.dataStore.length-1; outer >= 1; outer--) { 21 | // 내부 반복문은 배열의 처음부터 정렬 확인이 안된 배열의 위치까지 22 | for(let inner = 0; inner < outer; inner++) { 23 | if (this.dataStore[inner] > this.dataStore[inner+1]) { // 인접한 값을 비교하여 왼쪽 값이 오른쪽 값보다 더 크다면 24 | this.swap(inner, inner+1); // 두 값을 교환합니다. 25 | } 26 | } 27 | } 28 | } 29 | } 30 | ``` 31 | -------------------------------------------------------------------------------- /search/CompareTextSearch.spec.js: -------------------------------------------------------------------------------- 1 | import {expect} from 'chai'; 2 | import {BinarySearch} from "./binarySearch/BinarySearch"; 3 | import {LinearSearch} from "./linearSearch/LinearSearch"; 4 | const fs = require('fs'); 5 | 6 | describe('The text search test', ()=> { 7 | it('should be retrieved index number of a text which equal a specified text', ()=> { 8 | const file = fs.readFileSync('./spec/search/CompareTextSearch.spec.txt', 'utf-8').replace(/\r\n/gi, ' '); 9 | const words = file.split(' '); 10 | const linear = new LinearSearch(); 11 | linear.setArray(words); 12 | const binary = new BinarySearch(); 13 | binary.setArray(words); 14 | binary.quickSort(); 15 | 16 | let start = new Date().getTime(); 17 | expect(linear.search('testable.')).to.equal(63674); 18 | let stop = new Date().getTime(); 19 | let elapsed = stop - start; 20 | console.log(`The elapsed time of a linear search was ${elapsed} milliseconds`); 21 | 22 | start = new Date().getTime(); 23 | expect(binary.search('testable.')).to.equal(53686); 24 | stop = new Date().getTime(); 25 | elapsed = stop - start; 26 | console.log(`The elapsed time of a binary search was ${elapsed} milliseconds`); 27 | }) 28 | }); -------------------------------------------------------------------------------- /data_structures/queue/Queue.js: -------------------------------------------------------------------------------- 1 | export class Queue { 2 | constructor() { 3 | this.dataStore = []; 4 | } 5 | 6 | /** 7 | * 큐의 끝부분에 요소를 추가한다. 8 | * @param item 9 | */ 10 | enqueue(item) { 11 | this.dataStore.push(item); 12 | } 13 | 14 | /** 15 | * 큐의 앞부분의 값을 꺼내서 반환한다. 16 | * @returns {*} 17 | */ 18 | dequeue() { 19 | return this.dataStore.shift(); 20 | } 21 | 22 | /** 23 | * 큐의 맨 앞의 값을 확인한다. 24 | * @returns {*} 25 | */ 26 | front() { 27 | return this.dataStore[0]; 28 | } 29 | 30 | /** 31 | * 큐의 맨 뒷 값을 확인한다. 32 | * @returns {*} 33 | */ 34 | back() { 35 | return this.dataStore[this.dataStore.length-1]; 36 | } 37 | 38 | /** 39 | * 입력받은 구분자값으로 배열을 문자열로 반환한다. 40 | * 입력값이 없으면 기본적으로 ','로 구분하여 반환한다. 41 | * @param separator 42 | * @returns {string} 43 | */ 44 | toString(separator) { 45 | return this.dataStore.join(separator ? separator : ','); 46 | } 47 | 48 | /** 49 | * 큐가 비었는지 확인한다. 50 | */ 51 | empty() { 52 | if (this.dataStore.length === 0) { 53 | return true; 54 | } else { 55 | return false; 56 | } 57 | } 58 | count() { 59 | return this.dataStore.length; 60 | } 61 | } -------------------------------------------------------------------------------- /sort/RandomArray.js: -------------------------------------------------------------------------------- 1 | export class RandomArray { 2 | constructor(number = 0) { 3 | this.dataStore = []; 4 | this.pos = 0; 5 | this.numElements = number; 6 | if (this.numElements > 0) { 7 | for (let i = 0; i < number; i++) { 8 | this.dataStore[i] = i; 9 | } 10 | } 11 | } 12 | setData(number) { 13 | if (number) { 14 | this.numElements = number; 15 | } 16 | for (let i = 0; i < this.numElements; i++) { 17 | this.dataStore[i] = Math.floor(Math.random() * (this.numElements + 1)); 18 | }; 19 | /*for(let i = 0; i < this.numElements; i++) { 20 | this.dataStore[i] = Math.floor(Math.random() * (this.numElements + 1)); 21 | }*/ 22 | } 23 | clear() { 24 | this.dataStore.length = 0; 25 | } 26 | insert(element) { 27 | this.dataStore[this.dataStore.length] = element; 28 | } 29 | swap(index1, index2) { 30 | let temp = this.dataStore[index1]; 31 | this.dataStore[index1] = this.dataStore[index2]; 32 | this.dataStore[index2] = temp; 33 | } 34 | toString() { 35 | return this.dataStore.reduce((prev, curr) => prev += ', ' + curr); 36 | } 37 | getArray() { 38 | return this.dataStore; 39 | } 40 | setArray(arr) { 41 | this.numElements = arr.length; 42 | this.dataStore = Array.of(...arr); 43 | } 44 | } -------------------------------------------------------------------------------- /sort/quickSort/QuickSort.js: -------------------------------------------------------------------------------- 1 | import {RandomArray} from "../RandomArray"; 2 | 3 | export class QuickSort extends RandomArray { 4 | constructor(number = 0) { 5 | super(number); 6 | } 7 | 8 | /** 9 | * 10 | * @param low : 탐색할 가장 최소 인덱스 11 | * @param high : 탐색할 가장 최대 인덱스 12 | */ 13 | quickSort(low = 0, high = this.dataStore.length-1) { 14 | if (low < high) { // 당연히 최대인덱스는 최소 인덱스보다 커야 된다.. 15 | const pivotIndex = this.partition(low, high); 16 | 17 | this.quickSort(low, pivotIndex - 1); 18 | this.quickSort(pivotIndex + 1, high); 19 | } 20 | } 21 | partition(low, high) { 22 | const pivot = this.dataStore[high]; // 배열의 가장 오른쪽(끝) 값이 기준값으로 정한다. 23 | let i = low - 1; // 피벗값보다 작은 값들로 이뤄진 배열의 마지막 인덱스값 24 | for (let j = low; j < high; j++) { 25 | //현재 j인덱스의 값이 pivot보다 작거나 같다면 26 | if (this.dataStore[j] <= pivot) { 27 | i++; 28 | if (i !== j) { 29 | const temp = this.dataStore[i]; 30 | this.dataStore[i] = this.dataStore[j]; 31 | this.dataStore[j] = temp; 32 | } 33 | } 34 | } 35 | if (i+1 !== high) { 36 | const temp = this.dataStore[i+1]; 37 | this.dataStore[i+1] = this.dataStore[high]; 38 | this.dataStore[high] = temp; 39 | } 40 | return i+1; 41 | } 42 | } -------------------------------------------------------------------------------- /spec/advanced/LCS.spec.js: -------------------------------------------------------------------------------- 1 | import {expect} from 'chai'; 2 | import {LCS} from "../../advanced/LCS"; 3 | describe('LCS(Longest Common Subsequence) class test', ()=> { 4 | const isDebug = false; 5 | const word1 = 'ABCDGH'; 6 | const word2 = 'AEDFHR'; 7 | const result = 3; 8 | it('should be get max lsc number with iterative', ()=> { 9 | let lcs = new LCS(isDebug); 10 | const start = new Date().getTime(); 11 | expect(lcs.iter(word1, word2)).to.equal(result); 12 | const stop = new Date().getTime(); 13 | console.log(`iterative time - ${stop-start} milliseconds`); 14 | expect(lcs.iter(word1, '')).to.equal(0); 15 | }); 16 | it('should be get max lcs number with recursive DP', ()=> { 17 | let lcs = new LCS(isDebug); 18 | const start = new Date().getTime(); 19 | expect(lcs.recursionDP(word1, word2)).to.equal(result); 20 | const stop = new Date().getTime(); 21 | console.log(`recursive time - ${stop-start} milliseconds`); 22 | expect(lcs.recursion(word1, '')).to.equal(0); 23 | }) 24 | it('should be get max lcs number with recursive', ()=> { 25 | let lcs = new LCS(isDebug); 26 | const start = new Date().getTime(); 27 | expect(lcs.recursion(word1, word2)).to.equal(result); 28 | const stop = new Date().getTime(); 29 | console.log(`recursive time - ${stop-start} milliseconds`); 30 | expect(lcs.recursion(word1, '')).to.equal(0); 31 | }); 32 | }) -------------------------------------------------------------------------------- /search/binarySearch/BinarySearch.js: -------------------------------------------------------------------------------- 1 | import {RandomArray} from "../../sort/RandomArray"; 2 | import {QuickSort} from "../../sort/quickSort/QuickSort"; 3 | 4 | export class BinarySearch extends QuickSort { 5 | constructor(number = 0) { 6 | super(number); 7 | } 8 | search(val) { 9 | let upperBound = this.dataStore.length - 1; 10 | let lowerBound = 0; 11 | while (lowerBound <= upperBound) { 12 | const mid = Math.floor((upperBound + lowerBound) / 2); 13 | if (this.dataStore[mid] < val) { 14 | lowerBound = mid + 1; 15 | } else if (this.dataStore[mid] > val) { 16 | upperBound = mid - 1; 17 | } else { 18 | return mid; 19 | } 20 | } 21 | return -1; 22 | } 23 | count(val) { 24 | let count = 0; 25 | const position = this.search(val); 26 | if (position > -1) { 27 | ++count; 28 | for (let i = position-1; i > 0; i--) { 29 | if (this.dataStore[i] === val) { 30 | ++count; 31 | } else { 32 | break; 33 | } 34 | } 35 | for (let i = position+1; i < this.dataStore.length; i++) { 36 | if (this.dataStore[i] === val) { 37 | ++count; 38 | } else { 39 | break; 40 | } 41 | } 42 | } 43 | return count; 44 | } 45 | } -------------------------------------------------------------------------------- /sort/shellSort/ShellSort.js: -------------------------------------------------------------------------------- 1 | import {RandomArray} from "../RandomArray"; 2 | 3 | export class ShellSort extends RandomArray { 4 | constructor(number = 0) { 5 | super(number); 6 | this.gaps = [5, 3, 1]; 7 | } 8 | shellSort() { 9 | // console.log(this.toString()); 10 | for (let g = 0; g < this.gaps.length; g++) { 11 | for (let i = this.gaps[g]; i < this.dataStore.length; i++) { 12 | let temp = this.dataStore[i]; 13 | let j; 14 | for (j = i; j >= this.gaps[g] && this.dataStore[j-this.gaps[g]] > temp; j -= this.gaps[g]) { 15 | this.dataStore[j] = this.dataStore[j-this.gaps[g]]; 16 | // console.log(this.toString(), `===> i : ${i}, j : ${j}, temp : ${temp}`); 17 | } 18 | this.dataStore[j] = temp; 19 | // console.log(this.toString(), `===> i : ${i}`); 20 | } 21 | } 22 | // console.log(this.toString()); 23 | } 24 | dynamicShellSort() { 25 | let h = 0; 26 | while (h < this.dataStore.length) { 27 | h = 3 * h + 1; 28 | } 29 | while (h >= 1) { 30 | for (let i = h; i < this.dataStore.length; i++) { 31 | for(let j = i; j >= h && this.dataStore[j] > this.dataStore[j-h]; j -= h) { 32 | this.swap(j, j-h); 33 | } 34 | } 35 | h = (h-1)/3; 36 | } 37 | } 38 | setGaps(arr) { 39 | this.gaps = arr; 40 | } 41 | } -------------------------------------------------------------------------------- /data_structures/queue/PriorityQueue/BinaryHeapPriorityQueue.spec.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import {BinaryHeapPriorityQueue} from "./BinaryHeapPriorityQueue"; 3 | 4 | describe('BasicPriorityQueue Test', () => { 5 | it('case 1 : ascending queue', () => { 6 | const queue = new BinaryHeapPriorityQueue(); 7 | queue.enqueue('A', 100); 8 | queue.enqueue('B', 19); 9 | queue.enqueue('C', 36); 10 | queue.enqueue('D', 17); 11 | queue.enqueue('E', 3); 12 | queue.enqueue('F', 25); 13 | queue.enqueue('G', 1); 14 | queue.enqueue('H', 2); 15 | queue.enqueue('I', 7); 16 | 17 | const result = []; 18 | while(!queue.isEmpty()) { 19 | result.push(queue.dequeue().priority); 20 | } 21 | expect(result.join(', ')).to.equal('1, 2, 3, 7, 17, 19, 25, 36, 100'); 22 | }); 23 | 24 | it('case 1 : change priority', () => { 25 | const queue = new BinaryHeapPriorityQueue(); 26 | queue.enqueue('A', 100); 27 | queue.enqueue('B', 19); 28 | queue.enqueue('C', 36); 29 | queue.enqueue('D', 17); 30 | queue.enqueue('E', 3); 31 | queue.enqueue('F', 25); 32 | queue.enqueue('G', 1); 33 | queue.enqueue('H', 2); 34 | queue.enqueue('I', 7); 35 | 36 | queue.setPriority('G', 101); 37 | 38 | const result = []; 39 | while(!queue.isEmpty()) { 40 | result.push(queue.dequeue().priority); 41 | } 42 | expect(result.join(', ')).to.equal('2, 3, 7, 17, 19, 25, 36, 100, 101'); 43 | }) 44 | }) -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # 자바스크립트 자료구조와 알고리즘 2 | C#, 자바와 같은 일반적인 객체지향 언어에서 사용되는 자료구조와 알고리즘에 대한 자바스크립트식 구현 3 | 4 | ## 저장소 구조 5 | * advanced : 고급 알고리즘 구현 소스 및 가이드 6 | * data_structures : 자료구소 구현 소스 및 가이드 7 | * search : 검색 알고리즘 구현 소스 및 가이드 8 | * sort : 정렬 알고리즘 구현 소스 및 가이드 9 | * spec : 자료구조 및 알고리즘에 대한 테스트 코드 10 | 11 | ## 자료구조 12 | * [스택 (Stack)](./data_structures/stack) 13 | * [큐 (Queue)](./data_structures/queue) 14 | * [연결리스트 (Linked List)](./data_structures/linkedList) 15 | * [이진검색트리 (Binary Search Tree)](./data_structures/binarySearchTree) 16 | * [그래프 (Graph)](./data_structures/graph) 17 | 18 | ## 기본 알고리즘 19 | 20 | ### 정렬 알고리즘 21 | * [버블정렬 (Bubble Sort)](./sort/bubbleSort) 22 | * [선택정렬 (Selection Sort)](./sort/selectionSort) 23 | * [삽입정렬 (Insertion Sort)](./sort/insertionSort) 24 | * [셀정렬 (Shell Sort)](./sort/shellSort) 25 | * [병합정렬 (Merge Sort)](./sort/mergeSort) 26 | * [퀵정렬 (Quick Sort)](./sort/quickSort) 27 | 28 | ### 검색 알고리즘 29 | * [순차검색 (Linear Search)](./search/binarySearch) 30 | * [이진검색 (Binary Search)](./search/linearSearch) 31 | 32 | ### 고급 알고리즘 33 | * [동적 프로그래밍 (Dynamic Programming)](./advanced) 34 | * 탐욕 알고리즘 (Greed Algorithm) 35 | 36 | #### 그래프 알고리즘 37 | * [다익스트라 알고리즘](./advanced/graph/single_source_shortest_path) 38 | * [플로이드 워셜 알고리즘](./advanced/graph/all_pairs_shortest_path) 39 | * [최소신장 알고리즘](./advanced/graph/minimum_spanning_tree) 40 | * [TSP 알고리즘](./advanced/graph/travelling_salesman_problem) 41 | 42 | ### 예제들의 실행과 테스트 43 | 이 저장소에서 제공하는 예제들은 단위 테스트 코드를 포함하고 있습니다. 44 | 해당 코드들은 ES6 모듈 기반으로 작성이 되어 있어 실행을 하기에 앞서 트랜스파일링이 필요합니다. 45 | 46 | 단위 테스트 코드는 모카Mocha와 차이chai를 통해 구성되어 있습니다. 47 | 모카의 옵션으로 --require babel-register를 추가하여 바벨을 통한 트랜스파일링을 하셔야 합니다. -------------------------------------------------------------------------------- /data_structures/linkedList/DoublyLinkedList.js: -------------------------------------------------------------------------------- 1 | import {LinkedList} from "./LinkedList"; 2 | 3 | export class DoublyLinkedList extends LinkedList { 4 | constructor(item) { 5 | super(null); 6 | this.rear = null; 7 | if (item) { 8 | this.add(item); 9 | } 10 | } 11 | _addByIndex(item, index = this.length) { 12 | const newNode = new Node(item); 13 | if (this.head !== null) { 14 | const prevIndex = index ? index - 1 : this.length - 1; 15 | let prevNode = this.get(prevIndex); 16 | if (!prevNode) { 17 | return -1; 18 | } 19 | if (prevIndex < this.length - 1) { 20 | newNode.next = prevNode.next; 21 | prevNode.next.prev = newNode; 22 | } 23 | newNode.prev = prevNode; 24 | prevNode.next = newNode; 25 | } else { 26 | this.head = newNode; 27 | } 28 | 29 | if (!index || index === this.length) { 30 | this.rear = newNode; 31 | } 32 | ++this.length; 33 | return index; // 자신의 인덱스를 반환한다. 34 | } 35 | remove(index) { 36 | const currNode = this.get(index); 37 | currNode.next.prev = currNode.prev; 38 | currNode.prev.next = currNode.next; 39 | currNode.next = null; 40 | currNode.prev = null; 41 | --this.length; 42 | return true; 43 | } 44 | getLast() { 45 | return this.rear; 46 | } 47 | } 48 | 49 | export class Node { 50 | constructor(element) { 51 | this.element = element; 52 | this.prev = null; 53 | this.next = null; 54 | } 55 | } -------------------------------------------------------------------------------- /search/linearSearch/LinearSearch.spec.js: -------------------------------------------------------------------------------- 1 | import {expect} from 'chai'; 2 | import {LinearSearch} from "./LinearSearch"; 3 | 4 | describe('LinearSearch class test', () => { 5 | it('should be retrieved index number of equal a specified value in array', ()=> { 6 | let arr = new LinearSearch(); 7 | arr.setData(10000000); 8 | let start = new Date().getTime(); 9 | expect(arr.search(10000001)).to.equal(arr.getArray().findIndex( val => val === 10000001)); 10 | let stop = new Date().getTime(); 11 | let elapsed = stop - start; 12 | console.log(`The elapsed time was ${elapsed} milliseconds`); 13 | }); 14 | it('should be retrieved "-1" when is no a specified value', () => { 15 | let arr = new LinearSearch(); 16 | arr.setData(100); 17 | expect(arr.search(101)).to.equal(-1); 18 | }); 19 | it('should be retrieved a minimum value of array', () => { 20 | let arr = new LinearSearch(); 21 | arr.setData(100); 22 | expect(arr.searchMinValue()).to.equal(arr.getArray().reduce( (prev, curr) => prev < curr ? prev : curr)); 23 | }); 24 | it('should be retrieved a maximum value of array', () => { 25 | let arr = new LinearSearch(); 26 | arr.setData(100); 27 | expect(arr.searchMaxValue()).to.equal(arr.getArray().reduce( (prev, curr) => prev > curr ? prev : curr)); 28 | }); 29 | it('should be work to optimized the self-organized data when retrieved a specified value in array', ()=> { 30 | let arr = new LinearSearch(); 31 | arr.setArray([9, 4, 3, 5, 2, 1, 69, 48, 24, 21 ,56, 33]); 32 | expect(arr.search(5, true)).to.equal(2); 33 | expect(arr.search(4, true)).to.equal(0); 34 | }); 35 | }) -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | import {RandomArray} from "./sort/RandomArray"; 2 | 3 | var partition = function(array, left, right, pivotIndex) { // 정렬하는 부분 4 | var temp; 5 | var pivot = array[pivotIndex]; 6 | while (left <= right) { // 왼쪽, 오른쪽 수를 규칙과 비교해 다음 수로 넘어갑니다. 7 | while (array[left] < pivot) 8 | left++; 9 | while (array[right] > pivot) 10 | right--; 11 | if (left <= right) { // 왼쪽이 기준보다 크고, 오른쪽이 기준보다 작으면 12 | temp = array[left]; 13 | array[left] = array[right]; 14 | array[right] = temp; // 서로 바꿔줍니다. 15 | left++; 16 | right--; 17 | } 18 | } 19 | temp = array[left]; 20 | array[left] = array[pivotIndex]; 21 | array[pivotIndex] = temp; // 마지막으로 기준과 만난 수를 바꿔줍니다. 기준의 위치는 이제 i입니다. 22 | return left; 23 | }; 24 | 25 | var quickSort = function(array, left, right) { // 재귀하는 부분 26 | if (!left) left = 0; 27 | if (!right) right = array.length - 1; 28 | var pivotIndex = right; // 배열 가장 오른쪽의 수를 기준으로 뽑습니다. 29 | pivotIndex = partition(array, left, right - 1, pivotIndex); // right - 1을 하는 이유는 기준(현재 right)을 제외하고 정렬하기 위함입니다. 30 | if (left < pivotIndex - 1) 31 | quickSort(array, left, pivotIndex - 1); // 기준 왼쪽 부분 재귀 32 | if (pivotIndex + 1 < right) 33 | quickSort(array, pivotIndex + 1, right); // 기준 오른쪽 부분 재귀 34 | return array; 35 | }; 36 | 37 | // quickSort([6,10,1,9,4,8,2,7,3,5]); // [1,2,3,4,5,6,7,8] 38 | 39 | let arr = new RandomArray(); 40 | arr.setData(10000000); 41 | let start = new Date().getTime(); 42 | quickSort(arr.getArray()); 43 | let stop = new Date().getTime(); 44 | let bubbleSortElapsed = stop - start; 45 | console.log(`The elapsed time of BubbleSort was ${bubbleSortElapsed} milliseconds`); -------------------------------------------------------------------------------- /advanced/graph/travelling_salesman_problem/TSP.spec.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { TSP } from "./TSP"; 3 | 4 | describe('TSP Algorithm test', () => { 5 | it('case 1', () => { 6 | const graph = new TSP(); 7 | 8 | graph.addVertex('A'); 9 | graph.addVertex('B'); 10 | graph.addVertex('C'); 11 | graph.addVertex('D'); 12 | graph.addVertex('S'); 13 | 14 | graph.addEdge('A', 'B', 2); 15 | graph.addEdge('A', 'C', 5); 16 | graph.addEdge('A', 'D', 1); 17 | 18 | graph.addEdge('B', 'C', 3); 19 | graph.addEdge('B', 'D', 6); 20 | 21 | graph.addEdge('C', 'D', 4); 22 | 23 | graph.addEdge('S', 'D', 8); 24 | graph.addEdge('S', 'C', 7); 25 | graph.addEdge('S', 'B', 2); 26 | graph.addEdge('S', 'A', 5); 27 | 28 | const result = graph.tsp(); 29 | expect(result.minCost).to.equal(15); 30 | expect(result.minTour.join(', ')).to.equal('0, 4, 1, 2, 3, 0'); 31 | }); 32 | it('case 2', () => { 33 | const graph = new TSP(); 34 | graph.addVertex('A'); 35 | graph.addVertex('B'); 36 | graph.addVertex('C'); 37 | graph.addVertex('D'); 38 | 39 | const a = graph.combinations(0, 0, 4); 40 | console.log(a[0].toString(2)); 41 | }); 42 | 43 | it('case 3', () => { 44 | let a = 1 << 0; 45 | console.log('a', a, a.toString(2)); 46 | let b = (1 << 1) + (1 << 2); 47 | console.log(b, b.toString(2)); 48 | console.log(a & b); 49 | console.log(a & (b + a)); 50 | console.log(a | b); 51 | console.log(a | (b + a)); 52 | console.log((b + a).toString(2), a.toString(2), ((b + a) ^ a).toString(2)); 53 | }) 54 | }) -------------------------------------------------------------------------------- /sort/RandomArray.spec.js: -------------------------------------------------------------------------------- 1 | import {expect} from 'chai'; 2 | import {RandomArray} from "./RandomArray"; 3 | 4 | describe('RandomArray class test', () => { 5 | let arr; 6 | it('should be create RandomArray instance', () => { 7 | arr = new RandomArray(100); 8 | expect(arr.getArray().length).to.equal(100); 9 | }); 10 | it('should be fill random number', () => { 11 | arr = new RandomArray(100); 12 | arr.setData(); 13 | expect(arr.getArray().length).to.equal(100); 14 | }); 15 | it('should be clear array', () => { 16 | arr = new RandomArray(100); 17 | arr.clear(); 18 | expect(arr.getArray().length).to.equal(0); 19 | }); 20 | it('should be add element at last position', () => { 21 | arr = new RandomArray(100); 22 | arr.insert(1); 23 | expect(arr.getArray()[arr.getArray().length-1]).to.equal(1); 24 | }); 25 | it('should be exchange a value of two index', () => { 26 | arr = new RandomArray(); 27 | arr.insert(1); 28 | arr.insert(2); 29 | arr.swap(0, 1); 30 | expect(arr.getArray()[0]).to.equal(2); 31 | expect(arr.getArray()[1]).to.equal(1); 32 | }) 33 | it('should be get string of array', () => { 34 | arr = new RandomArray(100); 35 | expect(typeof arr.toString()).to.equal('string'); 36 | }); 37 | it('should be get array', () => { 38 | arr = new RandomArray(100); 39 | expect(arr.getArray() instanceof Array).to.be.true; 40 | }); 41 | it('should be set array as deep copy',() => { 42 | const test = [5, 4, 3, 2, 1]; 43 | arr = new RandomArray(); 44 | arr.setArray(test); 45 | expect(test !== arr.getArray()).to.be.true; 46 | }) 47 | }); -------------------------------------------------------------------------------- /search/linearSearch/LinearSearch.js: -------------------------------------------------------------------------------- 1 | import {RandomArray} from "../../sort/RandomArray"; 2 | 3 | export class LinearSearch extends RandomArray { 4 | constructor(number = 0) { 5 | super(number); 6 | } 7 | 8 | /** 9 | * 선형 검색 10 | * @param val 11 | * @param isSelfOrganized : 자체정렬 사용 여부 12 | * @returns {number} 13 | */ 14 | search(val, isSelfOrganized) { 15 | for (let i = 0; i < this.dataStore.length; i++) { 16 | if (this.dataStore[i] === val) { 17 | let resultIndex = i; 18 | if (isSelfOrganized && i > 0) { 19 | let dd = Math.floor(this.dataStore.length * 0.2); 20 | if (i > (this.dataStore.length * 0.2)) { 21 | this.swap(i, Math.floor(this.dataStore.length * 0.2)); 22 | resultIndex = Math.floor(this.dataStore.length * 0.2); 23 | } else { 24 | this.swap(i, i-1); 25 | resultIndex = i-1; 26 | } 27 | } 28 | return resultIndex; 29 | } 30 | } 31 | return -1; 32 | } 33 | searchMinValue() { 34 | let min = this.dataStore[0]; 35 | for (let i = 0; i < this.dataStore.length; i++) { 36 | if (this.dataStore[i] < min) { 37 | min = this.dataStore[i]; 38 | } 39 | } 40 | return min; 41 | } 42 | searchMaxValue() { 43 | let max = this.dataStore[0]; 44 | for (let i = 0; i < this.dataStore.length; i++) { 45 | if (this.dataStore[i] > max) { 46 | max = this.dataStore[i]; 47 | } 48 | } 49 | return max; 50 | } 51 | } -------------------------------------------------------------------------------- /sort/bubbleSort/CompareRuntime.spec.js: -------------------------------------------------------------------------------- 1 | import {expect} from 'chai'; 2 | import {BubbleSort} from "./BubbleSort"; 3 | import {SelectionSort} from "../selectionSort/SelectionSort"; 4 | import {InsertionSort} from "../insertionSort/InsertionSort"; 5 | 6 | describe('compare runtime each sort class', () => { 7 | const bubble = new BubbleSort(); 8 | const selection = new SelectionSort(); 9 | const insertion = new InsertionSort(); 10 | it('should be same a value of each array', ()=> { 11 | bubble.setData(10); 12 | selection.setArray(bubble.getArray()); 13 | insertion.setArray(bubble.getArray()); 14 | expect(bubble.toString() == selection.toString() && bubble.toString() == insertion.toString()).to.be.true; 15 | }); 16 | it('should be test', () => { 17 | let start = new Date().getTime(); 18 | bubble.bubbleSort(); 19 | let stop = new Date().getTime(); 20 | let bubbleSortElapsed = stop - start; 21 | console.log(`The elapsed time of BubbleSort was ${bubbleSortElapsed} milliseconds`); 22 | 23 | start = new Date().getTime(); 24 | selection.selectionSort(); 25 | stop = new Date().getTime(); 26 | let SelectionSortElapsed = stop - start; 27 | console.log(`The elapsed time of SelectionSort was ${SelectionSortElapsed} milliseconds`); 28 | 29 | start = new Date().getTime(); 30 | insertion.insertionSort(); 31 | stop = new Date().getTime(); 32 | let InsertionSortElapsed = stop - start; 33 | console.log(`The elapsed time of InsertionSort was ${InsertionSortElapsed} milliseconds`); 34 | 35 | expect(bubbleSortElapsed >= SelectionSortElapsed && SelectionSortElapsed >= InsertionSortElapsed).to.be.true; 36 | }) 37 | 38 | }) -------------------------------------------------------------------------------- /data_structures/stack/Stack.js: -------------------------------------------------------------------------------- 1 | export class Stack { 2 | constructor() { 3 | this.dataStore = []; 4 | } 5 | 6 | /** 7 | * Stack이 비어있는지 알려준다 8 | * @returns {boolean} 9 | */ 10 | empty() { 11 | if (this.dataStore.length === 0) { 12 | return true; 13 | } else { 14 | return false; 15 | } 16 | } 17 | 18 | /** 19 | * Stack에 요소(element)를 저장한다. 20 | * @param element 21 | */ 22 | push(element) { 23 | this.dataStore.push(element); 24 | } 25 | 26 | /** 27 | * Stack의 맨 위에 저장된 객체를 꺼낸다. 28 | * @returns {*} 29 | */ 30 | pop() { 31 | return this.dataStore.pop(); 32 | } 33 | 34 | /** 35 | * Stack의 맨 위에 저장된 객체를 반환한다. 36 | * 꺼내지는 않는다. 37 | * 비었을 때는 null을 반환한다. 38 | * @returns {*} 39 | */ 40 | peek() { 41 | if (this.dataStore.length > 0) { 42 | return this.dataStore[this.dataStore.length-1]; 43 | } else { 44 | return null; 45 | } 46 | } 47 | 48 | /** 49 | * Stack에서 주어진 객체(item)을 찾아서 그 위치를 반환한다. 50 | * @param item 51 | * @returns {number} 52 | */ 53 | search(item) { 54 | return this.dataStore.indexOf(item); 55 | } 56 | 57 | /** 58 | * Stack의 현재 길이를 반환한다. 59 | * @returns {number} 60 | */ 61 | size() { 62 | return this.dataStore.length; 63 | } 64 | 65 | /** 66 | * Stack의 모든 값을 비운다. 67 | */ 68 | clear() { 69 | this.dataStore.splice(0, this.dataStore.length); 70 | } 71 | 72 | /** 73 | * 입력받은 구분자값으로 배열을 문자열로 반환한다. 74 | * 입력값이 없으면 기본적으로 ','로 구분하여 반환한다. 75 | * @param separator 76 | * @returns {string} 77 | */ 78 | toString(separator) { 79 | return this.dataStore.join(separator ? separator : ','); 80 | } 81 | } -------------------------------------------------------------------------------- /data_structures/linkedList/DoublyLinkedList.spec.js: -------------------------------------------------------------------------------- 1 | import {expect} from 'chai'; 2 | import {DoublyLinkedList} from "./DoublyLinkedList"; 3 | 4 | describe('DoublyLinkedList class test', () => { 5 | 6 | let list; 7 | beforeEach(() => { 8 | list = new DoublyLinkedList(); 9 | }); 10 | 11 | it('should be add node', () => { 12 | expect(list.add('A')).to.equal(0); 13 | expect(list.get(0).element).to.equal('A'); 14 | expect(list.get(0).prev).to.be.null; 15 | expect(list.get(0).next).to.be.null; 16 | expect(list.add('C')).to.equal(1); 17 | expect(list.get(1).element).to.equal('C'); 18 | expect(list.get(1).prev.element).to.equal('A'); 19 | expect(list.get(1).next).to.be.null; 20 | list.add('B', 1); 21 | expect(list.get(1).element).to.equal('B'); 22 | expect(list.get(1).prev.element).to.equal('A'); 23 | expect(list.get(1).next.element).to.equal('C'); 24 | expect(list.get(0).next.element).to.equal('B'); 25 | expect(list.get(2).prev.element).to.equal('B'); 26 | }); 27 | it('should be remove node by index', () => { 28 | list.add('A'); 29 | list.add('B'); 30 | list.add('C'); 31 | list.add('E'); 32 | list.add('D'); 33 | const e = list.get(3); 34 | list.remove(3); 35 | expect(list.get(2).next.element).to.equal('D'); 36 | expect(list.get(3).prev.element).to.equal('C'); 37 | expect(e.prev).to.be.null; 38 | expect(e.next).to.be.null; 39 | }); 40 | it('should be get last node', () => { 41 | list.add('A'); 42 | expect(list.getLast().element).to.equal('A'); 43 | list.add('B'); 44 | expect(list.getLast().element).to.equal('B'); 45 | list.add('D'); 46 | expect(list.getLast().element).to.equal('D'); 47 | list.add('C', 2); 48 | expect(list.getLast().element).to.equal('D'); 49 | }) 50 | }) 51 | -------------------------------------------------------------------------------- /sort/mergeSort/MergeSort.js: -------------------------------------------------------------------------------- 1 | import {RandomArray} from "../RandomArray"; 2 | 3 | export class MergeSort extends RandomArray { 4 | constructor(number = 0) { 5 | super(number); 6 | } 7 | mergeArrays(arr, startLeft, stopLeft, startRight, stopRight) { 8 | let rightArr = new Array(stopRight - startRight + 1); 9 | let leftArr = new Array(stopLeft - startLeft + 1); 10 | let k = startRight; 11 | for(let i = 0; i < (rightArr.length-1);++i) { 12 | rightArr[i] = arr[k]; 13 | ++k; 14 | } 15 | 16 | k = startLeft; 17 | for(let i = 0; i < (leftArr.length-1);++i) { 18 | leftArr[i] = arr[k]; 19 | ++k; 20 | } 21 | 22 | rightArr[rightArr.length-1] = Infinity; 23 | leftArr[leftArr.length-1] = Infinity; 24 | let m = 0; 25 | let n = 0; 26 | for (let a = startLeft; a < stopRight; a++) { 27 | if (leftArr[m] <= rightArr[n]) { 28 | arr[a] = leftArr[m]; 29 | m++; 30 | } else { 31 | arr[a] = rightArr[n]; 32 | n++; 33 | } 34 | } 35 | //console.log('left array -', leftArr); 36 | //console.log('right array -', rightArr); 37 | } 38 | mergeSort() { 39 | if (this.dataStore.length < 2) { 40 | return; 41 | } 42 | let step = 1; 43 | let left, right; 44 | while(step < this.dataStore.length) { 45 | left = 0; 46 | right = step; 47 | while (right + step <= this.dataStore.length) { 48 | this.mergeArrays(this.dataStore, left, left + step, right, right + step); 49 | left = right + step; 50 | right = left + step; 51 | } 52 | if (right < this.dataStore.length) { 53 | this.mergeArrays(this.dataStore, left, left + step, right, this.dataStore.length); 54 | } 55 | step *= 2; 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /advanced/graph/all_pairs_shortest_path/Floyd.js: -------------------------------------------------------------------------------- 1 | import {Edge, Graph, Vertex} from "../../../data_structures/graph/Graph"; 2 | 3 | export class Floyd extends Graph { 4 | constructor() {super();} 5 | 6 | /** 7 | * 가중치 추가 8 | * @param data1 9 | * @param data2 10 | * @param weight 11 | */ 12 | addEdge(data1, data2, weight) { 13 | // 그래프에 저장된 각 정점의 인덱스를 준비합니다. 14 | const index1 = data1 instanceof Vertex ? this.findVertexIndex(data1.data) : this.findVertexIndex(data1); 15 | const index2 = data2 instanceof Vertex ? this.findVertexIndex(data2.data) : this.findVertexIndex(data2); 16 | // 그래프에 저장된 각 정점의 객체를 준비합니다. 17 | const obj1 = data1 instanceof Vertex ? data1 : this.findVertex(data1); 18 | const obj2 = data2 instanceof Vertex ? data2 : this.findVertex(data2); 19 | // 각 정점의 간선에 정점을 추가합니다. 20 | this.edges[index1].push(new Edge(obj2, weight)); 21 | this.edges[index2].push(new Edge(obj1, weight)); 22 | } 23 | 24 | search() { 25 | const arr = []; 26 | // 행렬 초기화 27 | for (let i = 0; i < this.vertices.length; i++) { 28 | arr[i] = []; 29 | for (let j = 0; j < this.vertices.length; j++) { 30 | if (i === j) { 31 | arr[i][j] = 0; 32 | } else { 33 | arr[i][j] = Infinity; 34 | } 35 | } 36 | } 37 | // 인접 행렬 값 추가 38 | this.edges.forEach((edges, i) => { 39 | edges.forEach(edge => { 40 | const j = this.findVertexIndex(edge.dest); 41 | arr[i][j] = edge.weight; 42 | }) 43 | }); 44 | 45 | for (let k = 0; k < this.vertices.length; k++) { 46 | for (let i = 0; i < this.vertices.length; i++) { 47 | for (let j = 0; j < this.vertices.length; j++) { 48 | if (arr[i][j] > arr[i][k] + arr[k][j]) { 49 | arr[i][j] = arr[i][k] + arr[k][j]; 50 | } 51 | } 52 | } 53 | } 54 | console.log(arr); 55 | } 56 | } -------------------------------------------------------------------------------- /spec/advanced/Knapsack.spec.js: -------------------------------------------------------------------------------- 1 | import {expect} from 'chai'; 2 | import {Knapsack} from "../../advanced/Knapsack"; 3 | describe('Knapsack class test', ()=> { 4 | const capacity = 16; 5 | const things = [ 6 | {w: 3, v: 4}, 7 | {w :4, v: 5}, 8 | {w: 7, v: 10}, 9 | {w: 8, v: 11}, 10 | {w: 9, v: 13} 11 | ] 12 | 13 | it('should be able to prepare a knapsack', ()=> { 14 | let knapsack = new Knapsack(capacity); 15 | expect(knapsack instanceof Knapsack).to.be.true; 16 | }); 17 | it('should be able to prepare something to put in a this knapsack', ()=> { 18 | let knapsack = new Knapsack(capacity); 19 | knapsack.initThings(things); 20 | expect(knapsack.getThings().length).to.equal(5); 21 | }); 22 | it('should be get a maximum value which put in a this knapsack, with a recursive method', ()=> { 23 | let knapsack = new Knapsack(capacity); 24 | expect(knapsack.recursion(things)).to.equal(23); 25 | }); 26 | it('should be get a maximum value which put in a this knapsack, with a recursive DP method', ()=> { 27 | let knapsack = new Knapsack(capacity); 28 | expect(knapsack.recursionDP(things)).to.equal(23); 29 | console.log('values of array by recursive DP'); 30 | for (let row = 0; row <= things.length; row++) { 31 | let cols = ''; 32 | for (let col = 0; col <= capacity; col++) { 33 | cols += `${knapsack.debugArray[row][col] ? knapsack.debugArray[row][col] : '-'}\t`; 34 | } 35 | console.log(cols); 36 | } 37 | }); 38 | it('should be get a maximum value wich put in a this knapsack, with a iterative DP method', ()=> { 39 | let knapsack = new Knapsack(capacity); 40 | expect(knapsack.iterDP(things)).to.equal(23); 41 | console.log('values of array by iterative DP'); 42 | for (let row = 0; row <= things.length; row++) { 43 | let cols = ''; 44 | for (let col = 0; col <= capacity; col++) { 45 | cols += `${knapsack.debugArray[row][col]}\t`; 46 | } 47 | console.log(cols); 48 | } 49 | }) 50 | }) -------------------------------------------------------------------------------- /data_structures/queue/PriorityQueue/BasicPriorityQueue.js: -------------------------------------------------------------------------------- 1 | export class BasicPriorityQueue { 2 | constructor(isAscending = true) { 3 | this.first = null; 4 | this.isAscending = isAscending; // 기본값은 오름차순 5 | } 6 | 7 | /** 8 | * 우선순위를 비교한다. 9 | * is가 then보다 우선순위가 높은지 판단한다. 10 | * 우선순위는 isAscending값에 따라 오름차순 또는 내림차순으로 결정한다. 11 | * @param is 12 | * @param then 13 | * @returns {boolean} 14 | */ 15 | isPriority(is, then) { 16 | if (this.isAscending) { // 오름차순 - is가 then보다 작으면 앞 17 | return is < then; 18 | } else { 19 | return is > then; // 내림차순 - is가 then보다 크면 앞 20 | } 21 | } 22 | 23 | isEmpty() { 24 | return this.first !== null ? false : true; 25 | } 26 | 27 | enqueue(key, priority) { 28 | const newNode = new Node(key, priority); 29 | if (!this.first || this.isPriority(priority, this.first.priority)) { 30 | // 큐의 값이 비었거나 새로운 노드가 첫 번째 노드보다 우선순위가 높다면 값을 교체한다. 31 | newNode.next = this.first; 32 | this.first = newNode; 33 | } else { 34 | let pointer = this.first; 35 | /** 36 | * 새로운 노드를 뒤에 삽입할 위치(포인터)를 검색한다. 37 | * 조건 1 : 포인터의 next가 있고(큐의 끝이 아니고) 38 | * 조건 2 : 포인터의 우선순위가 새로운 노드보다 높다면 반복한다 39 | */ 40 | while (pointer.next && this.isPriority(pointer.next.priority, priority)) { 41 | // 포인터를 포인터의 next로 교체한다. 42 | pointer = pointer.next; 43 | } 44 | // 정해진 위치(포인트) 뒤에 새로운 노드를 삽입한다. 45 | newNode.next = pointer.next; 46 | pointer.next = newNode; 47 | } 48 | } 49 | 50 | /** 51 | * 가장 앞에 큐를 꺼내고 새로운 first를 지정한다. 52 | * @returns {null|*} 53 | */ 54 | dequeue() { 55 | const first = this.first; 56 | this.first = this.first.next; 57 | return first; 58 | } 59 | } 60 | 61 | export class Node { 62 | constructor(key, priority) { 63 | this.key = key; 64 | this.priority = priority; 65 | this.next = null; 66 | } 67 | toString() { 68 | return { 69 | key: this.key, 70 | priority: this.priority, 71 | next: this.next.key 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /advanced/graph/minimum_spanning_tree/MST.js: -------------------------------------------------------------------------------- 1 | import {Edge, Graph, Vertex} from "../../../data_structures/graph/Graph"; 2 | import {BinaryHeapPriorityQueue} from "../../../data_structures/queue/PriorityQueue/BinaryHeapPriorityQueue"; 3 | 4 | export class MST extends Graph { 5 | constructor() {super();} 6 | 7 | /** 8 | * 가중치 추가 9 | * @param data1 10 | * @param data2 11 | * @param weight 12 | */ 13 | addEdge(data1, data2, weight) { 14 | // 그래프에 저장된 각 정점의 인덱스를 준비합니다. 15 | const index1 = data1 instanceof Vertex ? this.findVertexIndex(data1.data) : this.findVertexIndex(data1); 16 | const index2 = data2 instanceof Vertex ? this.findVertexIndex(data2.data) : this.findVertexIndex(data2); 17 | // 그래프에 저장된 각 정점의 객체를 준비합니다. 18 | const obj1 = data1 instanceof Vertex ? data1 : this.findVertex(data1); 19 | const obj2 = data2 instanceof Vertex ? data2 : this.findVertex(data2); 20 | // 각 정점의 간선에 정점을 추가합니다. 21 | this.edges[index1].push(new Edge(obj2, weight)); 22 | this.edges[index2].push(new Edge(obj1, weight)); 23 | } 24 | 25 | prim() { 26 | const visited = []; 27 | const heap = new BinaryHeapPriorityQueue(); 28 | this.vertices.forEach((v, i) => { 29 | heap.enqueue(i, i === 0 ? 0 : Infinity); 30 | }); 31 | 32 | while(!heap.isEmpty()) { 33 | const pointer = heap.dequeue(); 34 | // ? 포인터의 우선순위가 무한대이면..? 즉 연결된 간선이 없다면 빼나? 35 | visited.push(pointer.key); 36 | this.edges[pointer.key] 37 | .filter(edge => { 38 | const aa = visited.filter(v => { 39 | const thisIndex = this.findVertexIndex(edge.dest.data); 40 | return v === thisIndex 41 | }); 42 | return aa.length === 0; 43 | }) 44 | .forEach(edge => { 45 | const thisIndex = this.findVertexIndex(edge.dest.data); 46 | const temp = heap.getByKey(thisIndex); 47 | if (temp.priority > edge.weight) { 48 | heap.setPriority(thisIndex, edge.weight); 49 | } 50 | }) 51 | } 52 | 53 | return visited.map(i => this.vertices[i].data); 54 | } 55 | } -------------------------------------------------------------------------------- /data_structures/linkedList/DoublySimpleLinkedList.js: -------------------------------------------------------------------------------- 1 | export class DoublySimpleLinkedList { 2 | constructor() { 3 | this.head = null; 4 | this.length = 0; 5 | } 6 | find(index) { 7 | if (typeof index === 'number') { 8 | let currIndex = 0; 9 | if (index < 0 || index > this.length) { 10 | return null; 11 | } 12 | let target = this.head; 13 | while (currIndex !== index) { 14 | target = target.next; 15 | currIndex++; 16 | } 17 | return target; 18 | } 19 | } 20 | add(data, index) { 21 | let prevNode = this.head; 22 | let newNode = new Node(data); 23 | if (index !== null && index !== undefined) { 24 | prevNode = this.find(index); 25 | if (prevNode !== null) { 26 | let nextNode = prevNode.next; 27 | newNode.next = nextNode; 28 | nextNode.prev = newNode; // 다음 노드의 prev에 추가될 노드를 지정합니다. 29 | prevNode.next = newNode; 30 | newNode.prev = prevNode; // 추가될 노드의 prev에 이전 노드를 지정합니다. 31 | this.length++; 32 | } 33 | } else { 34 | if (prevNode) { 35 | while (prevNode.next) { 36 | prevNode = prevNode.next; 37 | } 38 | prevNode.next = newNode; 39 | newNode.prev = prevNode; // 추가될 노드의 prev에 이전 노드를 지정합니다. 40 | } else { 41 | this.head = newNode; 42 | } 43 | this.length++; 44 | } 45 | } 46 | remove(index) { 47 | let currNode = this.find(index); 48 | if (currNode) { 49 | if (currNode.prev) { 50 | currNode.prev.next = currNode.next; 51 | } 52 | if (currNode.next) { 53 | currNode.next.prev = currNode.prev; 54 | } 55 | if (index === 0) { // 첫번째 노드를 삭제한다면 head값을 재 지정합니다. 56 | this.head = currNode.next; 57 | } 58 | currNode.next = null; 59 | currNode.prev = null; 60 | this.length--; 61 | } 62 | } 63 | } 64 | 65 | export class Node { 66 | constructor(element) { 67 | this.element = element; 68 | this.next = null; 69 | this.prev = null; // 이전 노드 링크 70 | } 71 | } -------------------------------------------------------------------------------- /data_structures/linkedList/SimpleLinkedList.js: -------------------------------------------------------------------------------- 1 | export class SimpleLinkedList { 2 | constructor() { 3 | this.head = null; // 최초 삽입된 노드 참조 4 | this.length = 0; // 현재까지 삽입된 노드의 개수 5 | } 6 | find(index) { 7 | if (typeof index === 'number') { 8 | let currIndex = 0; // 현재 탐색 중인 노드의 인덱스입니다. 9 | if (index < 0 || index > this.length) { // 찾으려는 인덱스가 삽입된 노드 길이보다 크다면 10 | return null; // null 반환합니다. 11 | } 12 | let target = this.head; // 최초 시작 노드를 설정합니다. 13 | while (currIndex !== index) { // 찾는 인덱스와 현재 인덱스가 같지 않으면 14 | target = target.next; // 다음 인덱스의 노드로 변경합니다. 15 | currIndex++; 16 | } 17 | return target; // 탐색한 노드를 반환합니다. 18 | } 19 | } 20 | add(data, index) { // index가 지정되지 않으면 가장 마지막으로 노드를 삽입합니다. 21 | let target = this.head; 22 | let newNode = new Node(data); // 삽입할 노드를 생성합니다. 23 | if (index !== null && index !== undefined) { // 인덱스가 있다면 24 | target = this.find(index); //삽입할 위치를 탐색합니다. 25 | if (target !== null) { // 삽인된 노드가 하나라도 있다면 26 | newNode.next = target.next; // 삽입할 노드의 next에 검색한 노드의 next를 연결합니다. 27 | target.next = newNode; // 검색한 노드의 next는 삽입할 노드로 연결합니다. 28 | this.length++; // 삽입된 개수를 증가시킵니다. 29 | } else { // 해당 인덱스로 검색한 결과가 없으면 30 | // 아무 작업도 하지 않는다. 31 | } 32 | } else { // 인덱스가 없다면 33 | if (target) { // target이 null이 아니면, 즉, 리스트에 한개라도 삽입된 노드가 있다면 34 | while (target.next) { // target.next가 null일 때까지 탐색합니다. 즉, 가장 마지막 노드를 찾습니다. 35 | target = target.next; 36 | } 37 | target.next = newNode; // 마지막 노드의 next에 새로운 노드 newNode를 연결합니다. 38 | } else { // target이 null이면, 즉, 리스트에 삽입된 노드가 없다면 39 | this.head = newNode; // head에 newNode를 지정 40 | } 41 | this.length++; // 삽입된 개수를 증가시킵니다. 42 | } 43 | } 44 | remove(index) { 45 | let prevNode = this.find(index - 1); 46 | if (prevNode) { 47 | prevNode.next = prevNode.next.next; 48 | this.length--; 49 | } 50 | } 51 | } 52 | 53 | export class Node { 54 | constructor(element) { 55 | this.element = element; // 저장할 객체 또는 값 56 | this.next = null; // 다음 연결될 참조 노드 57 | } 58 | } -------------------------------------------------------------------------------- /advanced/Fibonacci.js: -------------------------------------------------------------------------------- 1 | export class Fibonacci { 2 | constructor() { 3 | this.val = []; 4 | } 5 | 6 | recursion(n) { 7 | if (n < 2) { 8 | return n; 9 | } else { 10 | return this.recursion(n - 1) + this.recursion(n - 2); 11 | } 12 | } 13 | 14 | dynamic(n) { 15 | let val = []; 16 | val[0] = 0; 17 | val[1] = 1; 18 | return this._dynamic(n, val); 19 | } 20 | _dynamic(n, val) { 21 | if (val[n] >= 0) { 22 | return val[n]; 23 | } 24 | val[n] = this._dynamic(n-1, val) + this._dynamic(n-2, val); 25 | return val[n]; 26 | } 27 | iter(n) { 28 | /*if (n < 2) { 29 | return n; 30 | } 31 | let result = 0; 32 | let prevOne = 1; // n-1 33 | let prevTwo = 0; // n-2 34 | for (let i = 2; i <= n; i++) { 35 | let tmp = prevOne + prevTwo; 36 | prevTwo = prevOne; 37 | prevOne = result; 38 | result = tmp; 39 | } 40 | 41 | return result;*/ 42 | 43 | // DP를 이용한 반복 44 | let val = []; 45 | val[0] = 0; 46 | val[1] = 1; 47 | for (let i = 2; i <= n; i++) { 48 | val[i] = val[i-1] + val[i-2]; 49 | } 50 | 51 | return val[n]; 52 | } 53 | lcs(word1, word2) { 54 | let max = 0; 55 | let index = 0; 56 | // 이차원 배열 설정 57 | let lcsArr = []; 58 | for (let i = 0; i <= word1.length; i++) { 59 | lcsArr[i] = []; 60 | } 61 | 62 | for (let i = 0; i <= word1.length; i++) { 63 | for (let j = 0; j <= word2.length; j++) { 64 | if (i === 0 || j === 0) { 65 | lcsArr[i][j] = 0; 66 | } else { 67 | if (word1[i-1] === word2[j-1]) { 68 | lcsArr[i][j] = lcsArr[i-1][i-1] + 1; 69 | } else { 70 | lcsArr[i][j] = 0; 71 | } 72 | } 73 | if (max < lcsArr[i][j]) { 74 | max = lcsArr[i][j]; 75 | index = i; 76 | } 77 | } 78 | } 79 | 80 | let str = ''; 81 | if (max === 0) { 82 | return ''; 83 | } else { 84 | for (let i = index-max; i <= max; ++i) { 85 | str += word2[i]; 86 | } 87 | return str; 88 | } 89 | } 90 | } -------------------------------------------------------------------------------- /data_structures/stack/Stack.spec.js: -------------------------------------------------------------------------------- 1 | import {assert, expect} from 'chai'; 2 | import {Stack} from './Stack'; 3 | 4 | describe('Stack class test', () => { 5 | const stack = new Stack(); 6 | 7 | it('Should be create stack class', () => { 8 | expect(stack instanceof Stack).to.be.true; 9 | }); 10 | 11 | it('Should be empty', () => { 12 | expect(stack.empty()).to.equal(true); 13 | }); 14 | 15 | it('Should be add', () => { 16 | stack.push('A'); 17 | expect(stack.size()).to.equal(1); 18 | stack.push('B'); 19 | expect(stack.size()).to.equal(2); 20 | stack.push('C'); 21 | expect(stack.size()).to.equal(3); 22 | }); 23 | 24 | it('Should be take it from the front and it not exist in Stack after', () => { 25 | expect(stack.pop()).to.equal('C'); 26 | expect(stack.peek()).to.equal('B'); 27 | }); 28 | 29 | it('Should be check a last value', () => { 30 | expect(stack.peek()).to.equal('B'); 31 | }); 32 | 33 | it('Should be search a value', () => { 34 | expect(stack.search('A')).to.equal(0); 35 | expect(stack.search('C')).to.equals(-1); 36 | }); 37 | 38 | it('Should be right current length', () => { 39 | expect(stack.size()).to.equal(2); 40 | }); 41 | 42 | it('Should be empty', () => { 43 | stack.clear(); 44 | expect(stack.size()).to.equal(0); 45 | }) 46 | 47 | it('Should be able to use to convert decimal to binary number', () => { 48 | const s = new Stack(); 49 | let num = 32; 50 | do { 51 | s.push(num % 2); 52 | num = Math.floor(num / 2); 53 | } while (num > 0); 54 | let converted = ''; 55 | while (s.peek() !== null) { 56 | converted += s.pop(); 57 | } 58 | expect(converted).to.equal('100000'); 59 | }); 60 | 61 | it('Should be able to use to check whether it is a palindrome', () => { 62 | const s = new Stack(); 63 | for(let w of 'hello') { 64 | s.push(w); 65 | } 66 | let rword = ''; 67 | while (s.size() > 0) { 68 | rword += s.pop(); 69 | } 70 | expect('hello' === rword).to.be.false; 71 | 72 | s.clear(); 73 | 74 | for(let w of 'racecar') { 75 | s.push(w); 76 | } 77 | rword = ''; 78 | while (s.size() > 0) { 79 | rword += s.pop(); 80 | } 81 | expect('racecar' === rword).to.be.true; 82 | }) 83 | }) -------------------------------------------------------------------------------- /data_structures/linkedList/LinkedList.js: -------------------------------------------------------------------------------- 1 | export class LinkedList { 2 | constructor(item) { 3 | this.head = null; 4 | this.length = 0; 5 | if (item) { 6 | this.add(item); 7 | } 8 | } 9 | add(item, index = this.length) { 10 | if (typeof index === 'number') { 11 | return this._addByIndex(item, index); 12 | } else if (typeof index === 'string') { 13 | // TODO. 14 | } 15 | } 16 | _addByIndex(item, index = this.length) { 17 | const newNode = new Node(item); 18 | if (this.head !== null) { 19 | const prevIndex = index ? index - 1 : this.length - 1; 20 | let prevNode = this.get(prevIndex); 21 | if (!prevNode) { 22 | return -1; 23 | } 24 | if (prevIndex < this.length - 1) { 25 | newNode.next = prevNode.next; 26 | } 27 | prevNode.next = newNode; 28 | } else { 29 | this.head = newNode; 30 | } 31 | ++this.length; 32 | return index; // 자신의 인덱스를 반환한다. 33 | } 34 | get(index) { 35 | let currNode; 36 | if (index >= 0) { 37 | currNode = this.head; 38 | for (let i = 1; i <= index; i++) { 39 | currNode = currNode.next; 40 | } 41 | } 42 | return currNode; 43 | } 44 | getFirst() { 45 | return this.head; 46 | } 47 | remove(index) { 48 | const prevNode = this.get(index - 1); 49 | if (prevNode) { 50 | prevNode.next = prevNode.next.next; 51 | } 52 | --this.length; 53 | return true; 54 | } 55 | indexOf(item) { 56 | let currNode = this.head; 57 | let index = 0; 58 | while (currNode.element !== item) { 59 | currNode = currNode.next; 60 | ++index; 61 | } 62 | return index; 63 | } 64 | size() { 65 | return this.length; 66 | } 67 | find(item) { 68 | let currNode = this.head; 69 | while (currNode.element !== item) { 70 | currNode = currNode.next; 71 | } 72 | return currNode; 73 | } 74 | toString() { 75 | let result = ''; 76 | let currNode = this.head; 77 | while(currNode !== null) { 78 | result += currNode.element; 79 | if (currNode.next != null) { 80 | result += ', '; 81 | } 82 | currNode = currNode.next; 83 | } 84 | return result; 85 | } 86 | } 87 | 88 | export class Node { 89 | constructor(element) { 90 | this.element = element; 91 | this.next = null; 92 | } 93 | } -------------------------------------------------------------------------------- /data_structures/linkedList/LinkedList.spec.js: -------------------------------------------------------------------------------- 1 | import {expect} from 'chai'; 2 | import {LinkedList} from "./LinkedList"; 3 | 4 | describe('LinkedList class test', () => { 5 | 6 | let list; 7 | beforeEach( () => { 8 | list = new LinkedList(); 9 | }) 10 | 11 | it('should be create LinkedList class', () => { 12 | expect(list instanceof LinkedList).to.be.true; 13 | }); 14 | it('should be get node by index', () => { 15 | expect(list.get(0)).to.be.null; 16 | }); 17 | it('should be add node', () => { 18 | expect(list.add('A')).to.equal(0); 19 | expect(list.add('B')).to.equal(1); 20 | expect(list.get(0).element).to.equal('A'); 21 | expect(list.get(1).element).to.equal('B'); 22 | }); 23 | it('should be add node at specified index', () => { 24 | list.add('A'); 25 | list.add('C'); 26 | expect(list.add('B', 1)).to.equal(1); 27 | expect(list.get(2).element).to.equal('C'); 28 | }); 29 | it('should be remove node of specified index', () => { 30 | list.add('A'); 31 | list.add('B'); 32 | list.add('C'); 33 | expect(list.remove(1)).to.be.true; 34 | expect(list.get(1).element).to.equal('C'); 35 | }); 36 | it('should be get first node', () => { 37 | list.add('A'); 38 | list.add('B'); 39 | list.add('C'); 40 | expect(list.getFirst().element).to.equal('A'); 41 | }) 42 | it('should be get node size', () => { 43 | list.add('A'); 44 | expect(list.size()).to.equal(1); 45 | list.add('B'); 46 | expect(list.size()).to.equal(2); 47 | list.add('C'); 48 | expect(list.size()).to.equal(3); 49 | list.remove(2); 50 | expect(list.size()).to.equal(2); 51 | list.remove(1); 52 | expect(list.size()).to.equal(1); 53 | list.remove(0); 54 | expect(list.size()).to.equal(0); 55 | }); 56 | it('should be get index of node it equal specified item', () => { 57 | list.add('A'); 58 | list.add('B'); 59 | list.add('C'); 60 | expect(list.indexOf('A')).to.equal(0); 61 | expect(list.indexOf('B')).to.equal(1); 62 | expect(list.indexOf('C')).to.equal(2); 63 | }); 64 | it('should be get node which equal specified item', () => { 65 | list.add('A'); 66 | list.add('B'); 67 | list.add('C'); 68 | expect(list.find('B').element).to.be.equal('B'); 69 | expect(list.find('B').next.element).to.be.equal('C') 70 | }) 71 | it('should be get string of LinkedList', () => { 72 | list.add('A'); 73 | list.add('B'); 74 | list.add('C'); 75 | expect(list.toString()).to.equal('A, B, C'); 76 | }) 77 | }) -------------------------------------------------------------------------------- /advanced/LCS.js: -------------------------------------------------------------------------------- 1 | export class LCS { 2 | constructor(isDebug = false) { 3 | this.isDebug = isDebug; 4 | } 5 | 6 | recursion(word1, word2) { 7 | if (word1.length <= 0 || word2.length <= 0) { 8 | return 0; 9 | } 10 | return this._recursion(word1, word2, word1.length, word2.length); 11 | } 12 | _recursion(word1, word2, i, j) { 13 | if (i === 0 || j === 0) { 14 | return 0; 15 | } else if (word1[i-1] === word2[j-1]) { 16 | return this._recursion(word1, word2, i-1, j-1) + 1; 17 | } else { 18 | return Math.max(this._recursion(word1, word2, i, j-1), this._recursion(word1, word2, i-1, j)); 19 | } 20 | } 21 | 22 | recursionDP(word1, word2) { 23 | if (word1.length <= 0 || word2.length <= 0) { 24 | return 0; 25 | } 26 | let lcsArr = []; 27 | for (let x = 0; x <= word1.length; x++) { 28 | lcsArr[x] = []; 29 | for (let y = 0; y <= word2.length; y++) { 30 | lcsArr[x][y] = null; 31 | } 32 | } 33 | const result = this._recursionDP(word1, word2, word1.length, word2.length, lcsArr); 34 | console.log(lcsArr); 35 | return result; 36 | } 37 | _recursionDP(word1, word2, i, j, lcsArr) { 38 | if (i === 0 || j === 0) { 39 | lcsArr[i][j] = 0; 40 | return lcsArr[i][j]; 41 | } else if (word1[i-1] === word2[j-1]) { 42 | if (!lcsArr[i][j]) { 43 | lcsArr[i][j] = this._recursionDP(word1, word2, i-1, j-1, lcsArr) + 1; 44 | } 45 | return lcsArr[i][j]; 46 | } else { 47 | if (!lcsArr[i][j-1]) { 48 | lcsArr[i][j-1] = this._recursionDP(word1, word2, i, j-1, lcsArr); 49 | } 50 | if (!lcsArr[i-1][j]) { 51 | lcsArr[i-1][j] = this._recursionDP(word1, word2, i-1, j, lcsArr); 52 | } 53 | lcsArr[i][j] = Math.max(lcsArr[i][j-1], lcsArr[i-1][j]); 54 | return lcsArr[i][j]; 55 | } 56 | } 57 | 58 | iter(word1, word2) { 59 | if (word1.length <= 0 || word2.length <= 0) { 60 | return 0; 61 | } 62 | 63 | let lcsArr = []; 64 | let max = 0; 65 | for (let i = 0; i <= word1.length; i++) { 66 | let str = ''; 67 | lcsArr[i] = []; 68 | for (let j = 0; j <= word2.length; j++) { 69 | if (i === 0 || j === 0) { 70 | lcsArr[i][j] = 0; 71 | } else if (word1[i-1] === word2[j-1]) { 72 | lcsArr[i][j] = lcsArr[i-1][j-1] + 1; 73 | max = lcsArr[i][j]; 74 | } else { 75 | lcsArr[i][j] = Math.max(lcsArr[i][j-1], lcsArr[i-1][j]); 76 | } 77 | str += lcsArr[i][j] + '\t'; 78 | } 79 | this.log(str); 80 | } 81 | return max; 82 | } 83 | 84 | log(str) { 85 | if (this.isDebug) { 86 | console.log(str); 87 | } 88 | } 89 | } -------------------------------------------------------------------------------- /data_structures/binarySearchTree/BST.spec.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { BST} from "./BST"; 3 | 4 | describe('BST test', () => { 5 | let bst; 6 | beforeEach(()=>{ 7 | bst = new BST(); 8 | }) 9 | it('should be create BST instance',()=> { 10 | expect(bst instanceof BST).to.be.true; 11 | }) 12 | 13 | it('should be add a node', ()=> { 14 | expect(bst.insert(6).data).to.equal(6); 15 | }) 16 | 17 | it('should be search by inOrder', ()=> { 18 | bst.insert(6); 19 | bst.insert(4); 20 | bst.insert(7); 21 | bst.insert(2); 22 | bst.insert(5); 23 | bst.insert(1); 24 | bst.insert(3); 25 | bst.insert(9); 26 | bst.insert(8); 27 | expect(bst.inOrder()).to.equal('1,2,3,4,5,6,7,8,9'); 28 | }) 29 | 30 | it('should be search by preOrder', ()=> { 31 | bst.insert(6); 32 | bst.insert(4); 33 | bst.insert(7); 34 | bst.insert(2); 35 | bst.insert(5); 36 | bst.insert(1); 37 | bst.insert(3); 38 | bst.insert(9); 39 | bst.insert(8); 40 | expect(bst.preOrder()).to.equal('6,4,2,1,3,5,7,9,8'); 41 | }) 42 | 43 | it('should be search by postOrder', ()=> { 44 | bst.insert(6); 45 | bst.insert(4); 46 | bst.insert(7); 47 | bst.insert(2); 48 | bst.insert(5); 49 | bst.insert(1); 50 | bst.insert(3); 51 | bst.insert(9); 52 | bst.insert(8); 53 | expect(bst.postOrder()).to.equal('1,3,2,5,4,8,9,7,6'); 54 | }) 55 | 56 | it('should be get a smallest value', ()=> { 57 | bst.insert(6); 58 | bst.insert(4); 59 | bst.insert(7); 60 | bst.insert(2); 61 | bst.insert(5); 62 | bst.insert(1); 63 | bst.insert(3); 64 | bst.insert(9); 65 | bst.insert(8); 66 | expect(bst.getMin()).to.equal(1); 67 | }) 68 | 69 | it('should be get a biggest value', ()=> { 70 | bst.insert(6); 71 | bst.insert(4); 72 | bst.insert(7); 73 | bst.insert(2); 74 | bst.insert(5); 75 | bst.insert(1); 76 | bst.insert(3); 77 | bst.insert(9); 78 | bst.insert(8); 79 | expect(bst.getMax()).to.equal(9); 80 | }) 81 | 82 | it('should be get a node which equal a specified value', ()=> { 83 | bst.insert(6); 84 | bst.insert(4); 85 | bst.insert(7); 86 | bst.insert(2); 87 | bst.insert(5); 88 | bst.insert(1); 89 | bst.insert(3); 90 | bst.insert(9); 91 | bst.insert(8); 92 | expect(bst.find(6).data).to.equal(6); 93 | }) 94 | 95 | it('should be remove a node which equal a specified value', ()=> { 96 | bst.insert(6); 97 | bst.insert(4); 98 | bst.insert(7); 99 | bst.insert(2); 100 | bst.insert(5); 101 | bst.insert(1); 102 | bst.insert(3); 103 | bst.insert(9); 104 | bst.insert(8); 105 | bst.remove(6) 106 | expect(bst.find(6)).to.be.null; 107 | }) 108 | }) -------------------------------------------------------------------------------- /data_structures/binarySearchTree/BinarySearchTree.js: -------------------------------------------------------------------------------- 1 | export class BinarySearchTree { 2 | constructor() { 3 | this.root = null; 4 | } 5 | insert(item) { 6 | const newNode = new Node(item); 7 | if (this.root) { 8 | this._insertNode(newNode, this.root); 9 | } else { 10 | this.root = newNode; 11 | } 12 | } 13 | _insertNode(newNode, currNode) { 14 | if (newNode.data < currNode.data) { 15 | if (currNode.left === null) { 16 | currNode.left = newNode; 17 | } else { 18 | this._insertNode(newNode, currNode.left); 19 | } 20 | } else if (newNode.data > currNode.data) { 21 | if (currNode.right === null) { 22 | currNode.right = newNode; 23 | } else { 24 | this._insertNode(newNode, currNode.right); 25 | } 26 | } else { 27 | // 같다. 28 | } 29 | } 30 | find(item, includeParent) { 31 | let parentNode = null; 32 | let currNode = this.root; 33 | while (item !== currNode.data) { 34 | if (item < currNode.data) { 35 | parentNode = currNode; 36 | currNode = currNode.left; 37 | } else if (item > currNode.data) { 38 | parentNode = currNode; 39 | currNode = currNode.right; 40 | } 41 | if (currNode === null) { 42 | return null; 43 | } 44 | } 45 | if (includeParent) { 46 | return { 47 | parentNode: parentNode, 48 | thisNode: currNode 49 | }; 50 | } else { 51 | return currNode; 52 | } 53 | 54 | } 55 | remove(item) { 56 | // this.root = this._removeNode(item, this.root); 57 | const findResult = this.find(item, true); 58 | if (!findResult) { 59 | return false; 60 | } 61 | const direction = findResult.parentNode.data > item ? 'left' : 'right'; 62 | if (!findResult.thisNode.left && !findResult.thisNode.right) { 63 | findResult.parentNode[direction] = null; 64 | } else if (!findResult.thisNode.left && findResult.thisNode.right) { 65 | findResult.parentNode[direction] = findResult.thisNode.right; 66 | } else if (findResult.thisNode.left && !findResult.thisNode.right) { 67 | findResult.parentNode[direction] = findResult.thisNode.left; 68 | } else { 69 | findResult.parentNode[direction] = this.getMin(findResult.thisNode.right); 70 | } 71 | } 72 | first() { 73 | return this.root; 74 | } 75 | getMin(root) { 76 | let currNode = root ? root : this.root; 77 | while (currNode.left !== null) { 78 | currNode = currNode.left; 79 | } 80 | return currNode; 81 | } 82 | getMax(root) { 83 | let currNode = root ? root : this.root; 84 | while (currNode.right !== null) { 85 | currNode = currNode.right; 86 | } 87 | return currNode; 88 | } 89 | } 90 | 91 | export class Node { 92 | constructor(data) { 93 | this.data = data; 94 | this.left = null 95 | this.right = null; 96 | } 97 | 98 | } -------------------------------------------------------------------------------- /advanced/graph/single_source_shortest_path/Dijkstra.js: -------------------------------------------------------------------------------- 1 | import {Edge, Graph, Vertex} from "../../../data_structures/graph/Graph"; 2 | 3 | export class Dijkstra extends Graph { 4 | constructor() { 5 | super(); 6 | } 7 | 8 | /** 9 | * 가중치 추가 10 | * @param data1 11 | * @param data2 12 | * @param weight 13 | */ 14 | addEdge(data1, data2, weight) { 15 | // 그래프에 저장된 각 정점의 인덱스를 준비합니다. 16 | const index1 = data1 instanceof Vertex ? this.findVertexIndex(data1.data) : this.findVertexIndex(data1); 17 | const index2 = data2 instanceof Vertex ? this.findVertexIndex(data2.data) : this.findVertexIndex(data2); 18 | // 그래프에 저장된 각 정점의 객체를 준비합니다. 19 | const obj1 = data1 instanceof Vertex ? data1 : this.findVertex(data1); 20 | const obj2 = data2 instanceof Vertex ? data2 : this.findVertex(data2); 21 | // 각 정점의 간선에 정점을 추가합니다. 22 | this.edges[index1].push(new Edge(obj2, weight)); 23 | this.edges[index2].push(new Edge(obj1, weight)); 24 | } 25 | 26 | 27 | shortest(source) { 28 | const sourceIndex = this.findVertexIndex(source); 29 | /** 30 | * 출발점 'from'으로부터 각 정점까지의 최단 경로를 나타내는 배열 31 | * 출발점과 동일한 정점은 0, 나머지는 Infinity로 지정한다. 32 | */ 33 | const shortest = this.vertices.map((vertex, index) => { 34 | return { 35 | distance: index === sourceIndex ? 0 : Infinity, // 최단 경로의 거리 36 | path: [] // 최단 경로의 배열 37 | } 38 | }); 39 | 40 | const visitedIndex = []; 41 | let currIndex = sourceIndex; 42 | while (currIndex) { // 방문 시작 43 | visitedIndex[currIndex] = true; // 현재 정점을 방문 이력에 남깁니다. 44 | // 현재 정점에 연결된 정점에 대한 최단 거리 갱신 45 | let nextIndex = null; // 다음에 방문 예정인 인덱스를 임시로 저장할 변수를 준비합니다. 46 | // 현재 정점으로 부터 갈 수 있는 모든 정점을 탐색합니다. 47 | this.edges[currIndex].forEach( edge => { 48 | const toIndex = this.findVertexIndex(edge.dest); 49 | if (!visitedIndex[toIndex]) { // 이미 방문한 정점이면 무시하고 아직 방문전인 정점만 탐색합니다. 50 | /* 51 | 1. shortest[toIndex].distance = sourceIndex에서 toIndex까지의 현재 저장된 거리 52 | 2. shortest[currIndex].distance = sourceIndex에서 currIndex까지의 현재 저장된 거리 53 | 3. shortest[currIndex].distance + edge.weight = currIndex를 경유하여 toIndex까지 가는 거리 54 | 1번과 3번 항목 중 1번 항목이 더 크다면 3번항목으로 1번 값을 변경한다. 55 | */ 56 | if (shortest[toIndex].distance > shortest[currIndex].distance + edge.weight) { 57 | shortest[toIndex].distance = shortest[currIndex].distance + edge.weight; // 거리 갱신 58 | shortest[toIndex].path = shortest[currIndex].path.concat(toIndex); // 경로 갱신 59 | } 60 | /** 61 | * 다음에 방문 예정인 인덱스가 아직 지정되지 않았거나 62 | * 다음에 방문 예정인 인덱스까지의 거리보다 현재 분석한 정점까지의 거리가 더 짧다면 63 | * 다음에 방문 예정인 인덱스를 현재 분석한 정점으로 지정한다. 64 | */ 65 | nextIndex = !nextIndex || shortest[nextIndex].distance > shortest[toIndex].distance ? toIndex : nextIndex; 66 | } 67 | }); 68 | currIndex = nextIndex; // 다음에 방문 예정인 인덱스를 확정한다. 69 | } 70 | return shortest; 71 | } 72 | } -------------------------------------------------------------------------------- /data_structures/binarySearchTree/BinarySearchTree.spec.js: -------------------------------------------------------------------------------- 1 | import {expect} from 'chai'; 2 | import {BinarySearchTree} from "./BinarySearchTree"; 3 | 4 | describe('BinarySearchTree class test', () => { 5 | let tree; 6 | beforeEach(()=> { 7 | tree = new BinarySearchTree(); 8 | }) 9 | it('should be created BinarySearchTree', () => { 10 | expect(tree instanceof BinarySearchTree).to.be.true; 11 | }); 12 | it('should be add node', () => { 13 | tree.insert(10); 14 | expect(tree.first().data).to.equal(10); 15 | }); 16 | it('should be add node at left of parent node when less than parent node value', () => { 17 | tree.insert(10); 18 | tree.insert(7); 19 | expect(tree.first().left.data).to.equal(7); 20 | }); 21 | it('should be add node at right of parent node when greater than parent node value', () => { 22 | tree.insert(10); 23 | tree.insert(7); 24 | tree.insert(15); 25 | expect(tree.first().right.data).to.equal(15); 26 | }); 27 | it('should be get first(root) node', () => { 28 | tree.insert(10); 29 | tree.insert(7); 30 | tree.insert(15); 31 | expect(tree.first().data).to.equal(10); 32 | }); 33 | it('should be get node which is a smallest value', () => { 34 | tree.insert(10); 35 | tree.insert(7); 36 | tree.insert(15); 37 | tree.insert(1); 38 | tree.insert(11); 39 | tree.insert(5); 40 | tree.insert(12); 41 | tree.insert(4); 42 | tree.insert(14); 43 | expect(tree.getMin().data).to.equal(1); 44 | }) 45 | it('should be get node which is a biggest value', () => { 46 | tree.insert(10); 47 | tree.insert(7); 48 | tree.insert(15); 49 | tree.insert(1); 50 | tree.insert(11); 51 | tree.insert(5); 52 | tree.insert(12); 53 | tree.insert(4); 54 | tree.insert(14); 55 | expect(tree.getMax().data).to.equal(15); 56 | }); 57 | it('should be get node which equal a specified value', () => { 58 | tree.insert(10); 59 | tree.insert(7); 60 | tree.insert(15); 61 | tree.insert(1); 62 | tree.insert(11); 63 | tree.insert(5); 64 | tree.insert(12); 65 | tree.insert(4); 66 | tree.insert(14); 67 | expect(tree.find(5).left.data).to.equal(4); 68 | expect(tree.find(14).left).to.be.null; 69 | }); 70 | describe('should be remove node', () => { 71 | beforeEach(() => { 72 | tree.insert(10); 73 | tree.insert(7); 74 | tree.insert(15); 75 | tree.insert(1); 76 | tree.insert(11); 77 | tree.insert(5); 78 | tree.insert(12); 79 | tree.insert(4); 80 | tree.insert(14); 81 | tree.insert(8); 82 | tree.insert(18); 83 | tree.insert(17); 84 | tree.insert(19); 85 | }); 86 | 87 | it('when left is null and right is null', () => { 88 | tree.remove(8); 89 | expect(tree.find(7, true).thisNode.right).to.be.null; 90 | }); 91 | it('when left is not null and right is null', () => { 92 | tree.remove(5); 93 | expect(tree.find(1, true).thisNode.right.data).to.equal(4); 94 | }); 95 | it('when left is null and right is not null', () => { 96 | tree.remove(12); 97 | expect(tree.find(11, true).thisNode.right.data).to.equal(14); 98 | }); 99 | it('when left is not null and right is not null', () => { 100 | tree.remove(15); 101 | expect(tree.find(10, true).thisNode.right.data).to.equal(17); 102 | }) 103 | }) 104 | }) -------------------------------------------------------------------------------- /data_structures/queue/PriorityQueue/BinaryHeapPriorityQueue.js: -------------------------------------------------------------------------------- 1 | export class BinaryHeapPriorityQueue { 2 | constructor(isAscending = true) { 3 | this.heap = [null]; 4 | this.isAscending = isAscending; // 기본값은 오름차순 5 | } 6 | 7 | /** 8 | * 우선순위를 비교한다. 9 | * is가 then보다 우선순위가 높은지 판단한다. 10 | * 우선순위는 isAscending값에 따라 오름차순 또는 내림차순으로 결정한다. 11 | * @param is 12 | * @param then 13 | * @returns {boolean} 14 | */ 15 | isPriority(is, then) { 16 | if (this.isAscending) { // 오름차순 - is가 then보다 작으면 앞 17 | return is < then; 18 | } else { 19 | return is > then; // 내림차순 - is가 then보다 크면 앞 20 | } 21 | } 22 | 23 | isEmpty() { 24 | return this.heap.length > 1 ? false : true; 25 | } 26 | 27 | heapifyUp() { 28 | const lastIndex = this.heap.length - 1; // 배열의 마지막 인덱스 29 | let lastParentNodeIndex = Math.floor(lastIndex / 2); // 자식을 가지고 있는 마지막 인덱스 30 | while (lastParentNodeIndex > 0) { 31 | this.heapify(lastParentNodeIndex); 32 | lastParentNodeIndex--; 33 | } 34 | } 35 | 36 | heapify(i) { 37 | const parentNode = this.heap[i]; // 부모 노드 38 | const leftNode = this.heap[i * 2]; // 왼쪽 자식 노드 39 | const rightNode = this.heap[i * 2 + 1]; // 오른쪽 자식 노드 40 | if (rightNode && this.isPriority(rightNode.priority, leftNode.priority)) { 41 | /** 42 | * 오른쪽 자식이 존재하면 왼쪽 자식과 오른쪽 자식 중 우선순위가 높은 노드를 43 | * 부모 노드와 우선순위를 비교하여 교체여부를 판단 44 | */ 45 | if (this.isPriority(rightNode.priority, parentNode.priority)) { 46 | this.swap(i, i * 2 + 1); 47 | this.heapify(i * 2 + 1); 48 | } 49 | } else if(leftNode) { 50 | /** 51 | * 오른쪽 자식이 없거나, 왼쪽 자식이 우선순위가 높으면 52 | * 부모 노드와 왼쪽 노드를 비교하여 교체여부를 판단 53 | * (현재 탐색중인 노드는 무조건 자식이 있으므로 왼쪽 노드는 반드시 존재함) 54 | */ 55 | if (this.isPriority(leftNode.priority, parentNode.priority)) { 56 | this.swap(i, i * 2); 57 | this.heapify(i * 2); 58 | } 59 | } 60 | } 61 | 62 | swap(a, b) { 63 | const temp = this.heap[a]; 64 | this.heap[a] = this.heap[b]; 65 | this.heap[b] = temp; 66 | } 67 | 68 | enqueue(key, priority) { 69 | const newNode = new Node(key, priority); 70 | this.heap.push(newNode); 71 | this.heapifyUp(); 72 | } 73 | 74 | /** 75 | * 가장 앞에 큐를 꺼내고 새로운 first를 지정한다. 76 | * @returns {null|*} 77 | */ 78 | dequeue() { 79 | if (this.heap.length > 2) { 80 | // heap 배열의 1번 인덱스와 마지막 인덱스를 교체 81 | this.swap(1, this.heap.length - 1); 82 | const result = this.heap.pop(); 83 | this.heapify(1); 84 | return result; 85 | } else { 86 | return this.heap.pop(); 87 | } 88 | } 89 | 90 | setPriority(key, priority) { 91 | const target = this.heap.filter(node => node !== null && node.key === key); 92 | target.forEach(node => { 93 | node.priority = priority; 94 | }); 95 | this.heapifyUp(); 96 | } 97 | 98 | getByKey(key) { 99 | return this.heap.find(node => node !== null && node.key === key); 100 | } 101 | } 102 | 103 | export class Node { 104 | constructor(key, priority) { 105 | this.key = key; 106 | this.priority = priority; 107 | } 108 | toString() { 109 | return { 110 | key: this.key, 111 | priority: this.priority, 112 | next: this.next.key 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /data_structures/stack/readme.md: -------------------------------------------------------------------------------- 1 | # 스택 - Stack 2 | * 스택은 가장 윗 부분에서만 자료의 추가 및 삭제가 일어납니다. 3 | * 실행속도가 빠르다. 4 | * 구현이 쉽다. 5 | * 수식 평가 및 함수 호출까지 프로그래밍의 다양한 영역에서 스택이 사용됩니다. 6 | 7 | ## 스택의 동작 8 | 스택은 잘 알려진 것처럼 후입선출(LIFO - Last-In, First-Out) 자료구조입니다. 9 | 10 | 뷔페에 쌓인 접시를 생각하면 쉽습니다. 11 | 손님들은 가장 위에 놓인 접시를 꺼내가고, 직원들 또한 새로운 접시를 가장 위에 쌓습니다. 12 | 13 | 후입선출의 특성으로 스택의 최상단에 있지 않은 요소에는 접근할 수 없습니다. 14 | 가장 아래의 요소에 접근하려면 모든 요소를 제거하는 수밖에 없습니다. 15 | 16 | 위 내용으로 부터 스택의 기능을 요약해 보겠습니다. 17 | * 요소를 뒤로 밀어 넣다. 18 | * 요소를 뒤에서 꺼낸다. 19 | * 가장 뒤에 요소가 무엇인지 확인한다. 20 | 21 | ## 스택의 구현 22 | 스택 또한 강력한 자바스크립트의 배열로 모두 구현이 가능합니다. 23 | 다만 우리는 스택이라는 제약사항을 고려하여 자바스크립트의 배열을 한번 감싸서 스택이라는 클래스로 만들어 볼 것입니다. 24 | 25 | ### 생성자 함수 26 | 우리는 내부적으로 자바스크립트의 배열을 이용해서 스택을 만들려고 합니다. 27 | 생성자 함수에 멤버로는 배열 하나만 있으면 될 것 같습니다. 28 | ```javascript 29 | class Stack { 30 | constructor() { 31 | this.dataStore = []; 32 | } 33 | } 34 | ``` 35 | 36 | ### 요소 밀어 넣기 37 | 스택에서 요소 추가는 가장 상단(우측)에 추가해야 합니다. 38 | 배열의 push() 메소드를 사용합시다. 39 | ```javascript 40 | class Stack { 41 | ... 42 | push(element) { 43 | this.dataStore.push(element); 44 | ``` 45 | 46 | ### 요소 꺼내기 47 | 스택에서 요소를 꺼내는 것은 가장 상단부터 하나씩 꺼내야 합니다. 48 | 배열의 pop() 메소드를 사용합시다. 49 | ```javascript 50 | class Stack { 51 | ... 52 | pop() { 53 | return this.dataStore.pop(); 54 | } 55 | } 56 | ``` 57 | 58 | ### 상단 요소 확인 59 | 스택의 최상단 요소를 확인하는 것은 넣거나 꺼내는 것과 같이 한줄 코드로는 불가능합니다. 60 | 우리는 스택 내부에 배열에 값을 저장을 하고 있었고, 그 배열의 현재 길이를 확인한 후 한개라도 요소가 있다면 마지막 인덱스를 지정해서 값을 확인해야 합니다. 61 | ```javascript 62 | class Stack { 63 | ... 64 | peek() { 65 | if (this.dataStore.length > 0) { 66 | return this.dataStore[this.dataStore.length - 1]; 67 | } else { 68 | return null; 69 | } 70 | } 71 | ``` 72 | 73 | 이렇게 최소한으로 스택이 가져야하는 기능을 구현해 보았습니다. 74 | 그 외의 기능들은 Stack.js 파일을 참고해보길 바랍니다. 75 | 76 | ## 스택의 이용 77 | 스택을 이용하면 다양한 문제를 손쉽게 해결할 수 있습니다. 78 | 79 | ### 진법 변환 80 | 숫자 A를 어떤 진법에서 다른 진법 B로 변환할 때는 어떠한 작업이 필요할까요? 81 | 82 | 1. A를 B로 나눈다. 83 | 1. 그 몫을 B로 나눈 몫이 0일 때까지 반복해서 나눈다. 84 | 1. 나누면서 발생한 나머지를 가장 최근에 나온 나머지부터 나열한다. 85 | 86 | 이것을 알고리즘으로 표현하면 어떻게 되나요? 87 | * A = 진법을 변환할 숫자 88 | * B = 변환할 진법 89 | * C = 나머지를 저장할 스택 90 | 1. A / B의 나머지를 C 스택에 추가 91 | 2. A / B의 몫을 A로 다시 치환 92 | 3. A / B의 몫이 0이 나올 때까지 1,2 반복 93 | 4. C의 스택을 하나씩 꺼내어 숫자인 문자열을 생성 94 | 95 | 코드로 구현하면 아래와 같습니다. 96 | 우리가 만든 Stack 클래스를 이용해서 mulBase라는 함수를 만들겠습니다. 97 | mulBase의 매개변수는 변환할 숫자 num과 변환할 진법 base를 받습니다. 98 | ```javascript 99 | function mulBase(num, base) { 100 | let s = new Stack(); 101 | let target = num; 102 | do { 103 | s.push(target % base); // 1. 104 | target = Math.floor(target / base); // 2. 105 | } while(target > 0); // 3. 106 | 107 | let result = ''; // 4. 108 | while(s.peek() !== null) { 109 | result += s.pop(); 110 | } 111 | return result; 112 | } 113 | ``` 114 | 코드의 주석에 위에서 작성한 알고리즘 번호를 달아 놓았습니다. 115 | 우리는 스택을 이용해서 간단한 진법 변환 함수를 만들어 보았습니다. 116 | 물론 이 진법 변환은 2~9진법까지만 사용할 수 있습니다. 117 | 118 | ### 회문 119 | 스택을 이용해서 회문인지 판단할 수 있습니다. 120 | 회문은 앞으로 읽으나 뒤로 읽으나 같은 단어, 구절, 숫자를 뜻 합니다. '토마토'같이 말이죠. 121 | 122 | 알고리즘은 간단합니다. 123 | 1. 문자열을 앞글자 하나씩 스택에 담습니다. 124 | 2. 스택에 담은 문자열을 하나씩 꺼내서 새로운 문자열로 만듭니다. 125 | 3. 기존 문자열과 새로운 문자열이 같은지 비교합니다. 126 | 127 | 우리는 Stack 클래스를 이용해서 isPalindrome함수를 만들어 보겠습니다. 128 | 이 함수는 매개변수로 회문여부를 조사할 문자열 word 하나만 받고, 결과값으로 true, false를 반환하도록 하겠습니다. 129 | ```javascript 130 | function isPalindrome(word) { 131 | let s = new Stack(); 132 | for (let c of word) { // 1. 133 | s.push(c); 134 | } 135 | 136 | let reverse = ''; 137 | while (s.peek() !== null) { // 2. 138 | reverse += s.pop(); 139 | } 140 | 141 | if (word === revers) { // 3. 142 | return true; 143 | } else { 144 | return false; 145 | } 146 | } 147 | ``` 148 | 이렇게 회문 검사 함수도 만들어 보았습니다. 149 | 150 | ### 재귀 151 | 스택은 컴퓨터 프로그래밍에서도 많이 사용됩니다. 152 | 그중 재귀를 구현할 때 스택을 사용합니다. 컴퓨터 프로그래밍에서 사용되는 재귀 프로시저를 우리가 만들기는 어렵고, 비슷하게 팩토리얼 함수를 재귀로 구현해보도록 하겠습니다. 153 | 154 | 5의 팩토리얼은 다음과 같이 습니다. 155 |
5! = 5 * 4 * 3 * 2 * 1 = 120
156 | 157 | n의 팩토리얼을 구하는 재귀적 함수 호출은 아래와 같습니다. 158 | ```javascript 159 | function factorial(n) { 160 | if (n === 0) return 1; 161 | return n * factorial(n-1); 162 | } 163 | ``` 164 | 165 | 우리가 만든 Stack 클래스를 이용해서 팩토리얼을 구하는 것을 시뮬레이션 해보도록 하겠습니다. 166 | 작업하기 전에 우리의 알고리즘을 표현해봅시다. 167 | 1. n부터 n을 1씩 빼가면서 n이 1보다 클 때까지 스택에 담습니다. 168 | 2. 스택에서 한개씩 꺼내서 곱합니다. 169 | 170 | ```javascript 171 | function fact(n) { 172 | let s = new Stack(); 173 | while (n > 1) { 174 | s.push(n--); 175 | } 176 | 177 | let result = 1; 178 | while (s.peek() !== null) { 179 | result *= s.pop(); 180 | } 181 | return result; 182 | } 183 | ``` 184 | 185 | 이상 자바스크립트의 스택 자료구조에 대해서 살펴보았습니다. -------------------------------------------------------------------------------- /advanced/Knapsack.js: -------------------------------------------------------------------------------- 1 | export class Knapsack { 2 | constructor(capacity = 0, things = null) { 3 | this.capacity = capacity; // 배낭의 무게 4 | this._w = []; 5 | this._v = []; 6 | this.initThings(things); 7 | this.debugArray = []; 8 | } 9 | 10 | initThings(things) { 11 | if (things) { 12 | this._w.length = 0; 13 | this._v.length = 0; 14 | things.forEach( val => { 15 | this._w.push(val.w); 16 | this._v.push(val.v); 17 | }) 18 | } 19 | } 20 | getThings() { 21 | return this._w.map( (val, i) => { 22 | return {w: val, v: this._v[i]}; 23 | }) 24 | } 25 | 26 | recursion(things) { 27 | if (things) { 28 | this.initThings(things); 29 | } 30 | return this._recursion(this.capacity, this._w.length); 31 | } 32 | 33 | /** 34 | * 35 | * @param capacity : 현재 배낭의 용량 36 | * @param position : 검사 중인 물건의 인덱스 ( 0, ..., n) 37 | * @returns {*} 38 | * @private 39 | */ 40 | _recursion(capacity, position) { 41 | if (position <= 0 || capacity <= 0) { 42 | return 0; 43 | } 44 | const w = this._w[position - 1]; 45 | const v = this._v[position - 1]; 46 | 47 | if (capacity < w) { 48 | // 배낭의 용량보다 현재 물건의 무게가 더 크다면 이전 배낭의 용량을 반환 49 | return this._recursion(capacity, position - 1); 50 | } else { 51 | // 배낭의 용량보다 물건의 무게가 작거나 같다면 52 | // 현재 검사중인 물건의 가치 + 현재 물건의 무게를 뺀 배낭 기준의 최대 가치를 더하거나 53 | // 현재 물건의 무게를 빼지않은 배낭 기준의 최대 가치 중 더 큰 값을 반환 54 | return Math.max( 55 | v + this._recursion(capacity - w, position - 1), 56 | this._recursion(capacity, position - 1) 57 | ) 58 | } 59 | } 60 | recursionDP(things) { 61 | if (things) { 62 | this.initThings(things); 63 | let arr = []; 64 | for (let i = 0; i <= this._w.length; i++) { 65 | arr[i] = []; 66 | } 67 | return this._recursionDP(this.capacity, this._w.length, arr); 68 | } else { 69 | return 0; 70 | } 71 | } 72 | 73 | /** 74 | * 75 | * @param capacity : 현재 배낭의 용량 76 | * @param position : 검사 중인 물건의 인덱스 ( 1, ..., n+1) **** 1부터 시작하여 n+1까지 저장 77 | * @param arr : 계산된 값을 저장할 이차원배열 78 | * @returns {*} 79 | * @private 80 | */ 81 | _recursionDP(capacity, position, arr) { 82 | if (position <= 0 || capacity <= 0) { 83 | if (!arr[position][capacity]) { 84 | arr[position][capacity] = 0; 85 | } 86 | return arr[position][capacity]; 87 | } 88 | const w = this._w[position - 1]; 89 | const v = this._v[position - 1]; 90 | 91 | if (!arr[position][capacity]) { 92 | if (capacity < w) { 93 | arr[position][capacity] = this._recursionDP(capacity, position - 1, arr); 94 | } else { 95 | arr[position][capacity] = Math.max( 96 | v + this._recursionDP(capacity - w, position - 1, arr), 97 | this._recursionDP(capacity, position - 1, arr) 98 | ) 99 | } 100 | } 101 | this.debugArray = arr; 102 | return arr[position][capacity]; 103 | } 104 | 105 | iterDP(things) { 106 | if (things) { 107 | this.initThings(things); 108 | let arr = []; 109 | for (let i = 0; i <= this.capacity; i++) { 110 | arr[i] = []; 111 | } 112 | return this._iterDP(this.capacity, this._w.length, arr); 113 | } else { 114 | return 0; 115 | } 116 | } 117 | _iterDP(capacity, size, arr) { 118 | let free = capacity; 119 | // row : 현재 검사중인 물건의 인덱스 + 1 120 | // col : 가능한 무게량 121 | // arr[row][col] : 한재 물건까지로 계산된 최대 가치 122 | for (let row = 0; row <= size; row++) { 123 | arr[row] = []; 124 | for (let col = 0; col <= capacity; col++) { 125 | const weight = this._w[row-1]; 126 | const value = this._v[row-1]; 127 | if (row === 0 || col === 0) { 128 | arr[row][col] = 0; 129 | } else if (weight > col) { 130 | arr[row][col] = arr[row-1][col]; 131 | } else { 132 | const i = col - weight > 0 ? col - weight : 0; 133 | arr[row][col] = Math.max( 134 | value + arr[row-1][i], 135 | arr[row-1][col] 136 | ) 137 | } 138 | } 139 | } 140 | this.debugArray = arr; 141 | return arr[size][capacity]; 142 | } 143 | } -------------------------------------------------------------------------------- /data_structures/binarySearchTree/BST.js: -------------------------------------------------------------------------------- 1 | export class BST { 2 | constructor() { 3 | this.root = null; 4 | } 5 | insert(data) { 6 | const newNode = new Node(data); // 추가할 데이터의 노드를 생성합니다. 7 | if (this.root === null) { // 루트노드에 값이 없으면 루트로 지정하고 종료합니다. 8 | this.root = newNode; 9 | return newNode; 10 | } 11 | let curr = this.root; // 현재 커서를 루트로 지정합니다. 12 | while (true) { // 반복합니다. 13 | if (data > curr.data) { // 추가할 데이터가 현재 노드값보다 크면 오른쪽을 탐색합니다. 14 | if (curr.right === null) { // 커서 오른쪽의 참조값이 null이면 15 | curr.right = newNode; // 추가할 노드를 커서의 right에 지정합니다. 16 | break; // 루프를 종료합니다. 17 | } else { // 커서 오른쪽의 참조값이 null이 아니면 18 | curr = curr.right; // 커서를 현재 커서의 오른쪽 노드로 지정합니다. 19 | } 20 | } else if (data < curr.data) { // 추가할 데이터가 현재 노드값보다 작으면 왼쪽을 탐색합니다. 21 | if (curr.left === null) { // 커서 왼쪽의 참조값이 null이면 22 | curr.left = newNode; // 추가할 노드를 커서의 left에 지정합니다. 23 | break; // 루프를 종료합니다. 24 | } else { // 커서 왼쪽의 참조값이 null이 아니면 25 | curr = curr.left; // 커서를 현재 커서의 왼쪽 노드로 지정합니다. 26 | } 27 | } else { 28 | break; // 현재 커서에 동일한 값이 이미 존재하므로 종료합니다. 29 | } 30 | } 31 | return newNode; 32 | } 33 | 34 | inOrder(node = this.root, queue = []) { 35 | if (node !== null) { 36 | this.inOrder(node.left, queue); // 왼쪽 노드부터 탐색하여 출력합니다. 37 | queue.push(node.data); // 왼쪽 노드를 다 출력 후 자신의 값을 출력합니다. 38 | this.inOrder(node.right, queue); // 오른쪽 노드를 출력합니다. 39 | } 40 | return queue.toString(); 41 | } 42 | 43 | preOrder(node = this.root, queue = []) { 44 | if (node !== null) { 45 | queue.push(node.data); // 자신의 값을 출력합니다. 46 | this.preOrder(node.left, queue); // 왼쪽 노드부터 탐색하여 출력합니다. 47 | this.preOrder(node.right, queue); // 오른쪽 노드를 출력합니다. 48 | } 49 | return queue.toString(); 50 | } 51 | 52 | postOrder(node = this.root, queue = []) { 53 | if (node !== null) { 54 | this.postOrder(node.left, queue); // 왼쪽 노드부터 탐색하여 출력합니다. 55 | this.postOrder(node.right, queue); // 오른쪽 노드를 출력합니다. 56 | queue.push(node.data); // 자신의 값을 출력합니다. 57 | } 58 | return queue.toString(); 59 | } 60 | 61 | getMin(node = this.root) { 62 | let curr = node; 63 | while (curr.left !== null) { 64 | curr = curr.left; 65 | } 66 | return curr.data; 67 | } 68 | 69 | getMax(node = this.root) { 70 | let curr = node; 71 | while (curr.right !== null) { 72 | curr = curr.right; 73 | } 74 | return curr.data; 75 | } 76 | 77 | find(value) { 78 | let curr = this.root; 79 | while (curr && curr.data !== value) { 80 | if (curr.data > value) { 81 | curr = curr.left; 82 | } else if (curr.data < value) { 83 | curr = curr.right; 84 | } 85 | } 86 | return curr; 87 | } 88 | 89 | remove(value) { 90 | const node = this._remove(value, this.root); 91 | if (node) { 92 | this.root = node; 93 | } 94 | } 95 | 96 | _remove(value, node = this.root) { 97 | if (node === null) { 98 | return null; 99 | } 100 | if (node.data === value) { 101 | if (node.left === null && node.right === null) { 102 | // 자식이 둘다 null이면 103 | return null; //null을 반환한다. 104 | } else if (node.left === null && node.right !== null) { 105 | // 왼쪽 자식이 null이면 106 | return node.right; // 노드의 오른쪽 자식을 반환한다. 107 | } else if (node.left !== null && node.right === null) { 108 | // 오른쪽 자식이 null이면 109 | return node.left; // 노드의 왼쪽 자식을 연결한다. 110 | } else if (node.left !== null && node.right !== null) { 111 | // 두 자식이 모두 있으면 112 | const temp = this.getMax(node.left); // 왼쪽 자식 중 가장 큰 값을 조회한다. 113 | node.data = temp; // 노드의 값을 왼쪽 자식 중 가장 큰 값으로 변경한다. 114 | node.left = this._remove(temp, node.left); // 노드의 왼쪽 트리에서 왼쪽 자식 중 가장 큰 값을 삭제한다. 115 | return node; // 수정된 노드를 반환한다. 116 | } 117 | } else if (node.data < value) { 118 | node.right = this._remove(value, node.right); 119 | return node; 120 | } else if (node.data > value) { 121 | node.left = this._remove(value, node.left); 122 | return node; 123 | } 124 | } 125 | } 126 | 127 | export class Node { 128 | constructor(data, left = null, right = null) { 129 | this.data = data; 130 | this.left = left; 131 | this.right = right; 132 | } 133 | } -------------------------------------------------------------------------------- /data_structures/queue/readme.md: -------------------------------------------------------------------------------- 1 | # 큐 - Queue 2 | * 큐의 자료 삽입은 뒷 부분에서만 일어납니다. 3 | * 큐의 앞부분에서는 데이터가 삭제됩니다. 4 | * 큐는 일어난 순서대로 데이터를 저장하는 자료구조로 스택과는 반대의 순서로 처리됩니다. 5 | * 운영 체제의 프로세스 처리 순서, 프린트 스풀러 등에서 큐를 사용합니다. 6 | 7 | ## 큐의 동작 8 | 큐는 선입선출(First-In, First-Out) 자료구조입니다. 9 | 은행의 또는 식료품점 등의 대기줄을 생각하시면 됩니다. 가장 먼저온 사람부터 처리가 됩니다. 10 | 11 | 큐에 요소를 삽입하는 동작을 인큐(enqueue), 12 | 요소를 삭제하는 동작을 데큐(dequeue)라고 합니다. 13 | 인큐는 큐의 끝부분부터 데큐는 큐의 앞부분부터 처리됩니다. 14 | 15 | 위 내용으로 부터 큐의 기능을 요약해 보겠습니다. 16 | * 요소를 뒤로 밀어 넣다. 17 | * 요소를 앞에서 꺼낸다. 18 | * 가장 앞의 요소가 무엇인지 확인한다. 19 | 20 | ## 큐의 구현 21 | 큐 또한 강력한 자바스크립트의 배열로 모두 구현이 가능합니다. 22 | 다만 우리는 큐라는 제약사항을 고려하여 자바스크립트의 배열을 한번 감싸서 큐이라는 클래스로 만들어 볼 것입니다. 23 | 24 | ### 생성자 함수 25 | 우리는 내부적으로 자바스크립트의 배열을 이용해서 큐를 만들려고 합니다. 26 | 생성자 함수에 멤버로는 배열 하나만 있으면 될 것 같습니다. 27 | ```javascript 28 | class Queue { 29 | constructor() { 30 | this.dataStore = []; 31 | } 32 | } 33 | ``` 34 | 35 | ### 요소 밀어 넣기 36 | 스택에서 요소 추가는 가장 상단(우측, 끝)에 추가해야 합니다. 37 | 배열의 push() 메소드를 사용하여 enqueue 메서드를 만듭니다. 38 | ```javascript 39 | class Queue { 40 | ... 41 | enqueue(element) { 42 | this.dataStore.push(element); 43 | ``` 44 | 45 | ### 요소 꺼내기 46 | 큐에서 요소를 꺼내는 것은 가장 앞부터 하나씩 꺼내야 합니다. 47 | 배열의 shift() 메소드를 사용하여 dequeue 메서드를 만듭니다. 48 | ```javascript 49 | class Queue { 50 | ... 51 | dequeue() { 52 | return this.dataStore.shift(); 53 | } 54 | } 55 | ``` 56 | 57 | ### 맨 앞 요소 확인 58 | 스택과 달리 첫번째 요소를 확인하는 것은 간단합니다. 59 | ```javascript 60 | class Queue { 61 | ... 62 | peek() { 63 | return this.dataStore[0]; 64 | } 65 | ``` 66 | 67 | 이렇게 최소한으로 큐가 가져야하는 기능을 구현해 보았습니다. 68 | 그 외의 기능들은 Queue.js 파일을 참고해보길 바랍니다. 69 | 70 | ## 큐의 이용 71 | 큐는 순서대로 줄을 서는 상황에서 순차적으로 처리하기 위해 자주 사용됩니다. 72 | 73 | ### 은행의 대기순서 처리 74 | 은행에 창구직원은 A, B, C로 3명이서 근무를 하고 있습니다. 75 | 대기줄 D는 각기 다른 업무를 보는 손님들이 줄 서 있습니다. 76 | 77 | 이렇게 구현할려고 합니다. 78 | 먼저 대기줄 D라는 큐를 만들고 큐에는 업무 소요시간을 뜻하는 정수를 담습니다. 79 | 창구직원 A, B, C는 큐에서 한 고객씩 업무를 처리하고 업무가 끝나면 다음 고객을 받습니다. 80 | 81 | 먼저 고객 대기 큐는 간단하게 고객의 업무 소요 시간만 담도록 하겠습니다. 82 | 83 | 그리고 창구직원 클래스를 만들겠습니다. 84 | 창구직원마다 이름이 있겠죠? 생성자 함수에 이름을 받아 저장합니다. 85 | 그리고 고객을 받아서 업무를 처리하는 메서드가 필요합니다. 86 | 메서드는 처리할 대기 큐를 받아서 해당 큐에서 고객을 한명씩 처리합니다. 87 | ```javascript 88 | class BankTeller { 89 | constructor(name) { // 창구직원의 이름을 받아 저장합니다 90 | this.name = name; 91 | } 92 | receive(q) { // 처리할 고객대기 큐를 받아 한명씩 처리합니다. 93 | if (q.peek() !== undefined) { // 대기 고객이 있으면 업무를 처리합니다. 94 | let guest = q.dequeue(); // 고객을 한명 받습니다. 95 | console.log(this.name, guest); // 업무 처리 중 96 | if (typeof guest === 'number') { 97 | setTimeout(() => { // 업무처리가 완료되면 98 | this.receive(q); // 다음 고객을 받습니다. 99 | }, guest * 1000); 100 | } 101 | } 102 | } 103 | } 104 | ``` 105 | 창구직원 클래스가 완성되었습니다. 106 | 이제 은행을 오픈하고 고객을 받아 보겠습니다. 107 | ```javascript 108 | // 대기큐와 창구직원을 생성합니다. 109 | const queue = new Queue(); 110 | const A = new BankTeller('A'); 111 | const B = new BankTeller('B'); 112 | const C = new BankTeller('C'); 113 | 114 | // 고객을 초기화합니다. 큐에 저장되는 값은 고객의 업무 처리 시간입니다. 115 | queue.enqueue(5); 116 | queue.enqueue(2); 117 | queue.enqueue(2); 118 | queue.enqueue(4); 119 | queue.enqueue(6); 120 | queue.enqueue(2); 121 | queue.enqueue(3); 122 | queue.enqueue(1); 123 | queue.enqueue(5); 124 | queue.enqueue(2); 125 | 126 | // 창구직원이 업무를 시작합니다. 127 | A.receive(queue); 128 | B.receive(queue); 129 | C.receive(queue); 130 | ``` 131 | 이러게 큐를 이용해서 은행 업무를 만들어 보았습니다. 132 | 133 | ### 큐로 데이터 정렬하기 134 | 단순히 시뮬레이션하는 용도 뿐만 아니라 데이터를 정렬할 때도 큐를 사용할 수 있습니다. 135 | 큐를 사용해서 정렬을 하는 기수 정렬이라는 간단한 알고리즘이 있습니다. 136 | 137 | 기수정렬은 0부터 9까지 단위의 수를 담을 큐가 필요합니다. 138 | 그리고 정렬할 숫자들의 가장 오른쪽 한 자리 숫자씩 비교해서 0부터 9까지 분류를 합니다. 139 | 분류된 숫자를 다시 순서대로 꺼내서 하나의 배열로 합치고 그렇게 한자리씩 올라가면서 재정렬 재정렬 하다보면 나도 모르게 정렬이 되어 있습니다. 140 | 141 | 글로만 설명하려니 한계가 있습니다. 142 | 코드로 표현해 보겠습니다. 143 | ```javascript 144 | // 정렬할 숫자 배열을 정의합니다. 145 | const nums = [1, 10, 1001, 5, 55, 5005, 501, 15, 101, 1005, 11, 505, 5001, 51, 105]; 146 | // 정렬 숫자들의 최대 자릿수를 지정합니다. 물론 위 배열을 검사해서 동적으로 최대 자릿수를 구하셔도 됩니다. 147 | const maxdigit = 4; 148 | 149 | //0~9 단위의 큐를 준비합니다. 각 큐는 다시 배열에 담아서 관리하겠습니다. 150 | const queues = []; // 큐들을 담을 배열 151 | for (let i = 0; i < 10; i++) { 152 | queues.push(new Queue()); 153 | } 154 | 155 | let target = nums; // 정렬할 배열을 별도 변수에 저장합니다. 156 | for (let i = 0; i < maxdigit; i++) { // 비교할 최대 자리수 만큼 반복해서 수행합니다. 157 | target.forEach( v => { // 정렬할 값을 배열에서 하나씩 꺼내서 비교합니다. 158 | // 1. 비교할 값 v가 현재 검사하는 자리수보다 작으면 무조건 0큐에 넣습니다. 현재 자릿수는 Math.pow(10, i)로 확인합니다. 159 | // 2. 위 대상이 아닌 경우, 현재 검사 자릿수를 추출하여 해당 큐에 값을 담습니다. 160 | const idx = v >= Math.pow(10, i) ? Math.floor(v / Math.pow(10, i)) % 10 : 0; 161 | queues[idx].enqueue(v); 162 | }); 163 | 164 | // 분류한 큐를 다시 하나의 배열로 담습니다. 165 | target = []; 166 | queues.forEach(v => { 167 | while (!v.empty()) { 168 | target.push(v.dequeue()); 169 | } 170 | }); 171 | 172 | // 반복해서 작업합니다. 173 | } 174 | ``` 175 | 176 | 큐에 넣고 빼고를 반복하면서 정렬하는 기수정렬 알고리즘 구현을 완료하였습니다. 177 | 178 | 이만 자바스크립트로 만든 큐에 대한 이야기를 마치겠습니다. 179 | 저장소에 올려진 Queue.js에는 이 가이드 문서와 다른 형태로 개발된 메소드명이 있습니다. 180 | 이것은 큐의 앞에 값뿐만 아니라 가장 뒤에 값도 확인하는 메소드를 추가하다 보니 네이밍 규칙을 맞출려고 변경해 놓았음을 알려드립니다. 181 | 182 | ## 다양한 큐 구현 183 | * [우선순위 큐](./PriorityQueue) -------------------------------------------------------------------------------- /data_structures/queue/Queue.spec.js: -------------------------------------------------------------------------------- 1 | import {assert, expect} from 'chai'; 2 | import {Queue} from "./Queue"; 3 | const fs = require('fs'); 4 | 5 | describe('Queue class test', () => { 6 | const queue = new Queue(); 7 | it('Should be create Queue class', () => { 8 | expect(queue instanceof Queue).to.be.true; 9 | }); 10 | 11 | it('Should be empty', () => { 12 | expect(queue.empty()).to.equal(true); 13 | }); 14 | 15 | it('Should be add', () => { 16 | queue.enqueue('A'); 17 | expect(queue.back()).to.equal('A'); 18 | queue.enqueue('B'); 19 | expect(queue.back()).to.equal('B'); 20 | queue.enqueue('C'); 21 | expect(queue.back()).to.equal('C'); 22 | }); 23 | 24 | it('Should be get it from the front and it not exist in Queue after', () => { 25 | expect(queue.dequeue()).to.equal('A'); 26 | expect(queue.front()).to.not.equals('A'); 27 | }); 28 | 29 | it('Should be get it from the front without deleting it in Queue', () => { 30 | expect(queue.front()).to.equals('B'); 31 | expect(queue.front()).to.equals('B'); 32 | }); 33 | 34 | it('Should be get it from the most back without deleting it in Queue', () => { 35 | expect(queue.back()).to.equal('C'); 36 | }); 37 | 38 | it('Should be get string from Queue data', () => { 39 | expect(queue.toString()).to.equal('B,C'); 40 | expect(queue.toString(';')).to.equal('B;C'); 41 | }); 42 | 43 | it('should be get count which remain queue', () => { 44 | expect(queue.count()).to.equal(2); 45 | }); 46 | 47 | it('Should be able to use to dance party simulation', () => { 48 | const males = new Queue(); 49 | const females = new Queue(); 50 | const file = fs.readFileSync('./data_structures/queue/Queue.spec.txt', 'utf-8'); 51 | file.split('\n').forEach( v => { 52 | const dancer = v.split(' '); 53 | if (dancer[0] === 'F') { 54 | females.enqueue({ 55 | sex: dancer[0], 56 | name: dancer[1] 57 | }); 58 | } else { 59 | males.enqueue({ 60 | sex: dancer[0], 61 | name: dancer[1] 62 | }) 63 | } 64 | }) 65 | 66 | const matchDancer = new Queue(); 67 | while (!females.empty() && !males.empty()) { 68 | matchDancer.enqueue(`Female dancer is: ${females.dequeue().name} and the male dancer is: ${males.dequeue().name}`); 69 | } 70 | 71 | expect(matchDancer.dequeue()).to.equal('Female dancer is: Allison and the male dancer is: Frank'); 72 | expect(matchDancer.dequeue()).to.equal('Female dancer is: Cheryl and the male dancer is: Mason'); 73 | expect(matchDancer.dequeue()).to.equal('Female dancer is: Jennifer and the male dancer is: Clayton'); 74 | expect(matchDancer.dequeue()).to.equal('Female dancer is: Aurora and the male dancer is: Raymond'); 75 | 76 | expect(females.count()).to.equal(0); 77 | expect(males.count()).to.equal(3); 78 | }); 79 | 80 | it('Should be able to use to radix sort', () => { 81 | const nums = [1, 10, 1001, 5, 55, 5005, 501, 15, 101, 1005, 11, 505, 5001, 51, 105]; 82 | const maxdigit = 4; 83 | 84 | const queues = []; 85 | for (let i = 0; i < 10; i++) { 86 | queues.push(new Queue()); 87 | } 88 | 89 | let target = nums; 90 | for (let i = 0; i < maxdigit; i++) { 91 | target.forEach( v => { 92 | const idx = v >= Math.pow(10, i) ? Math.floor(v / Math.pow(10, i)) % 10 : 0; 93 | queues[idx].enqueue(v); 94 | }); 95 | 96 | target = []; 97 | queues.forEach(v => { 98 | while (!v.empty()) { 99 | target.push(v.dequeue()); 100 | } 101 | }); 102 | } 103 | expect(target.join(',')).to.equal('1,5,10,11,15,51,55,101,105,501,505,1001,1005,5001,5005'); 104 | }) 105 | 106 | it('Should be able to use to bank simulation', () => { 107 | class BankTeller { 108 | constructor(name) { 109 | this.name = name; 110 | } 111 | receive(q) { 112 | if (q.front() !== undefined) { 113 | let guest = q.dequeue(); 114 | console.log(this.name, guest); 115 | if (typeof guest === 'number') { 116 | setTimeout(()=> { 117 | this.receive(q); 118 | }, guest * 1000); 119 | } 120 | } 121 | }; 122 | } 123 | 124 | queue.enqueue(5); 125 | queue.enqueue(2); 126 | queue.enqueue(2); 127 | queue.enqueue(4); 128 | queue.enqueue(6); 129 | queue.enqueue(2); 130 | queue.enqueue(3); 131 | queue.enqueue(1); 132 | queue.enqueue(5); 133 | queue.enqueue(2); 134 | 135 | const A = new BankTeller('A'); 136 | const B = new BankTeller('B'); 137 | const C = new BankTeller('C'); 138 | A.receive(queue); 139 | B.receive(queue); 140 | C.receive(queue); 141 | }) 142 | }) -------------------------------------------------------------------------------- /advanced/graph/travelling_salesman_problem/TSP.js: -------------------------------------------------------------------------------- 1 | import {Edge, Graph, Vertex} from "../../../data_structures/graph/Graph"; 2 | 3 | export class TSP extends Graph { 4 | constructor() { 5 | super(); 6 | this.w = []; 7 | this.dp = []; 8 | } 9 | 10 | /** 11 | * 가중치 추가 12 | * @param data1 13 | * @param data2 14 | * @param weight 15 | */ 16 | addEdge(data1, data2, weight) { 17 | // 그래프에 저장된 각 정점의 인덱스를 준비합니다. 18 | const index1 = data1 instanceof Vertex ? this.findVertexIndex(data1.data) : this.findVertexIndex(data1); 19 | const index2 = data2 instanceof Vertex ? this.findVertexIndex(data2.data) : this.findVertexIndex(data2); 20 | // 그래프에 저장된 각 정점의 객체를 준비합니다. 21 | const obj1 = data1 instanceof Vertex ? data1 : this.findVertex(data1); 22 | const obj2 = data2 instanceof Vertex ? data2 : this.findVertex(data2); 23 | // 각 정점의 간선에 정점을 추가합니다. 24 | this.edges[index1].push(new Edge(obj2, weight)); 25 | this.edges[index2].push(new Edge(obj1, weight)); 26 | } 27 | 28 | tsp() { 29 | /** 정점간의 가중치 (거리)를 담은 이차원 배열 */ 30 | this.w = []; 31 | for (let i = 0; i < this.vertices.length; i++) { 32 | this.w[i] = []; 33 | for (let j = 0; j < this.vertices.length; j++) { 34 | let temp; 35 | temp = this.edges[i].find(edge => { 36 | const aa = this.findVertexIndex(edge.dest); 37 | return this.findVertexIndex(edge.dest) === j 38 | } ); 39 | this.w[i][j] = temp ? temp.weight : i === j ? 0 : Infinity; 40 | } 41 | } 42 | 43 | /** dp[to][visited] 44 | * 방문한 경로로부터 to까지의 최단 거리를 저장하는 배열 45 | */ 46 | this.dp = []; 47 | for (let i = 0; i < this.vertices.length; i++) { 48 | this.dp[i] = []; 49 | for (let j = 0; j < (1 << this.vertices.length); j++) { 50 | this.dp[i][j] = -1; 51 | } 52 | } 53 | 54 | const s = 0; 55 | this.setup(); 56 | this.solve(s); 57 | const minCost = this.findMinCost(s); 58 | const minTour = this.findOptimalTour(s); 59 | return {minCost, minTour}; 60 | } 61 | 62 | setup() { 63 | for (let i = 1; i < this.vertices.length; i++) { 64 | this.dp[i][1 << 0 | 1 << i] = this.w[0][i]; 65 | } 66 | } 67 | solve(s) { 68 | for (let r = 3; r <= this.vertices.length; r++) { 69 | for (let subset of this.combinations(0, 0 , r)) { 70 | if (this.notIn(s, subset)) continue; 71 | for (let next = 0; next < this.vertices.length; next++) { 72 | if (next === s || this.notIn(next, subset)) continue; 73 | const state = subset ^ ( 1 << next); 74 | let minDist = Infinity; 75 | for (let e = 0; e < this.vertices.length; e++) { 76 | if (e === s || e === next || this.notIn(e, subset)) continue; 77 | const newDistance = this.dp[e][state] + this.w[e][next]; 78 | if (newDistance < minDist) { 79 | minDist = newDistance; 80 | } 81 | this.dp[next][subset] = minDist; 82 | } 83 | } 84 | } 85 | } 86 | } 87 | notIn(i, subset) { 88 | return ((1 << i) & subset) === 0; 89 | } 90 | combinations(set, at, r, subset = []) { 91 | if (r === 0) { 92 | subset.push(set); 93 | } else { 94 | for (let i = at; i < this.vertices.length; i++) { 95 | set = set | (1 << i); 96 | subset = this.combinations(set, i + 1, r - 1, subset); 97 | set = set & ~(1 << i); 98 | } 99 | } 100 | return subset; 101 | } 102 | findMinCost(s) { 103 | const END_STATE = (1 << this.vertices.length) - 1; 104 | let minTourCost = Infinity; 105 | for (let e = 0; e < this.vertices.length; e++) { 106 | if (e === s) continue; 107 | const tourCost = this.dp[e][END_STATE] + this.w[e][s]; 108 | if (tourCost < minTourCost) { 109 | minTourCost = tourCost; 110 | } 111 | } 112 | return minTourCost; 113 | } 114 | findOptimalTour(s) { 115 | let lastIndex = s; 116 | let state = (1 << this.vertices.length) - 1; 117 | const tour = []; 118 | for (let i = this.vertices.length - 1; i >= 1; i--) { 119 | let index = -1; 120 | for (let j = 0; j < this.vertices.length; j++) { 121 | if (j === s || this.notIn(j, state)) continue; 122 | if (index === -1) index = j; 123 | const prevDist = this.dp[index][state] + this.w[index][lastIndex]; 124 | const newDist = this.dp[j][state] + this.w[j][lastIndex]; 125 | if (newDist < prevDist) index = j; 126 | } 127 | tour[i] = index; 128 | state = state ^ (1 << index); 129 | lastIndex = index; 130 | } 131 | tour[0] = tour[this.vertices.length] = s; 132 | return tour; 133 | } 134 | } -------------------------------------------------------------------------------- /data_structures/graph/Graph.spec.js: -------------------------------------------------------------------------------- 1 | import {expect} from 'chai'; 2 | import {Graph} from "./Graph"; 3 | 4 | describe('Graph class test', () => { 5 | let graph; 6 | beforeEach(() => { 7 | graph = new Graph(); 8 | }); 9 | it('should be create graph', () => { 10 | expect(graph instanceof Graph).to.be.true; 11 | }); 12 | it('should be add vertex', () => { 13 | graph.addVertex('A'); 14 | graph.addVertex('B'); 15 | expect(graph.vertices.length).to.equal(2); 16 | }); 17 | it('should be get vertex of equal a specified value', () => { 18 | graph.addVertex('A'); 19 | graph.addVertex('B'); 20 | expect(graph.findVertex('A').data).to.equal('A'); 21 | }) 22 | it('should be get vertex index of equal a specified value', () => { 23 | graph.addVertex('A'); 24 | graph.addVertex('B'); 25 | expect(graph.findVertexIndex('A')).to.equal(0); 26 | expect(graph.findVertexIndex('B')).to.equal(1); 27 | }) 28 | it('should be remove vertex', () => { 29 | graph.addVertex('A'); 30 | graph.addVertex('B'); 31 | graph.removeVertex('A'); 32 | expect(graph.vertices.length).to.equal(1); 33 | expect(graph.findVertexIndex('B')).to.equal(0); 34 | }); 35 | it('should be add edge of A to B', () => { 36 | graph.addVertex('A'); 37 | graph.addVertex('B'); 38 | graph.addEdge('A', 'B'); 39 | expect(graph.findEdge('A')[0].dest.data).to.equal('B'); 40 | expect(graph.findEdge('B')[0].dest.data).to.equal('A'); 41 | expect(graph.findEdge('A', 'B')[0].dest.data).to.equal('B'); 42 | }); 43 | it('should be remove edge of A to B', () => { 44 | graph.addVertex('A'); 45 | graph.addVertex('B'); 46 | graph.addEdge('A', 'B'); 47 | graph.removeEdge('A', 'B'); 48 | expect(graph.findEdge('A').length).to.equal(0); 49 | expect(graph.findEdge('B').length).to.equal(0); 50 | graph.addVertex('C'); 51 | graph.addEdge('A', 'B'); 52 | graph.addEdge('A', 'C'); 53 | graph.removeEdge('A'); 54 | expect(graph.findEdge('A').length).to.equal(0); 55 | expect(graph.findEdge('B').length).to.equal(0); 56 | expect(graph.findEdge('C').length).to.equal(0); 57 | }); 58 | it('should be get string of graph', () => { 59 | graph.addVertex(0); 60 | graph.addVertex(1); 61 | graph.addVertex(2); 62 | graph.addVertex(3); 63 | graph.addVertex(4); 64 | graph.addEdge(0, 1); 65 | graph.addEdge(0, 2); 66 | graph.addEdge(1, 3); 67 | graph.addEdge(2, 4); 68 | expect(graph.toString()).to.equal('0 -> 1 2\n1 -> 0 3\n2 -> 0 4\n3 -> 1\n4 -> 2\n'); 69 | }); 70 | it('should be search to dfs', () => { 71 | graph.addVertex('A'); 72 | graph.addVertex('X'); 73 | graph.addVertex('G'); 74 | graph.addVertex('H'); 75 | graph.addVertex('P'); 76 | graph.addVertex('E'); 77 | graph.addVertex('Y'); 78 | graph.addVertex('M'); 79 | graph.addVertex('J'); 80 | graph.addEdge('A', 'X'); 81 | graph.addEdge('X', 'G'); 82 | graph.addEdge('X', 'H'); 83 | graph.addEdge('G', 'H'); 84 | graph.addEdge('G', 'P'); 85 | graph.addEdge('H', 'E'); 86 | graph.addEdge('H', 'P'); 87 | graph.addEdge('E', 'M'); 88 | graph.addEdge('E', 'Y'); 89 | graph.addEdge('Y', 'M'); 90 | graph.addEdge('M', 'J'); 91 | const result = []; 92 | graph.dfs('A', (val) => { 93 | result.push(val.data); 94 | }); 95 | expect(result.join(',')).to.equal('A,X,G,H,E,M,Y,J,P'); 96 | }); 97 | it('should be search to bfs', () => { 98 | graph.addVertex('A'); 99 | graph.addVertex('X'); 100 | graph.addVertex('G'); 101 | graph.addVertex('H'); 102 | graph.addVertex('P'); 103 | graph.addVertex('E'); 104 | graph.addVertex('Y'); 105 | graph.addVertex('M'); 106 | graph.addVertex('J'); 107 | graph.addEdge('A', 'X'); 108 | graph.addEdge('X', 'G'); 109 | graph.addEdge('X', 'H'); 110 | graph.addEdge('G', 'H'); 111 | graph.addEdge('G', 'P'); 112 | graph.addEdge('H', 'E'); 113 | graph.addEdge('H', 'P'); 114 | graph.addEdge('E', 'M'); 115 | graph.addEdge('E', 'Y'); 116 | graph.addEdge('Y', 'M'); 117 | graph.addEdge('M', 'J'); 118 | const result = []; 119 | graph.bfs('A', (val) => { 120 | result.push(val.data); 121 | }); 122 | expect(result.join(',')).to.equal('A,X,G,H,P,E,M,Y,J'); 123 | }); 124 | 125 | it('should be find the shortest path', () => { 126 | graph.addVertex('A'); 127 | graph.addVertex('X'); 128 | graph.addVertex('G'); 129 | graph.addVertex('H'); 130 | graph.addVertex('P'); 131 | graph.addVertex('E'); 132 | graph.addVertex('Y'); 133 | graph.addVertex('M'); 134 | graph.addVertex('J'); 135 | graph.addEdge('A', 'X'); 136 | graph.addEdge('X', 'G'); 137 | graph.addEdge('X', 'H'); 138 | graph.addEdge('G', 'H'); 139 | graph.addEdge('G', 'P'); 140 | graph.addEdge('H', 'E'); 141 | graph.addEdge('H', 'P'); 142 | graph.addEdge('E', 'M'); 143 | graph.addEdge('E', 'Y'); 144 | graph.addEdge('Y', 'M'); 145 | graph.addEdge('M', 'J'); 146 | const path = graph.pathFromTo('A', 'J'); 147 | expect(path.join('->')).to.equal('A->X->H->E->M->J'); 148 | graph.addEdge('P', 'J'); 149 | const path2 = graph.pathFromTo('A', 'J'); 150 | expect(path2.join('->')).to.equal('A->X->G->P->J'); 151 | }); 152 | it('should be use to topological sort', () => { 153 | graph.addVertex('CS1'); 154 | graph.addVertex('CS2'); 155 | graph.addVertex('Data Structures'); 156 | graph.addVertex('Assembly Language'); 157 | graph.addVertex('Operating Systems'); 158 | graph.addVertex('Algorithms'); 159 | graph.addEdge('CS2', 'Data Structures'); 160 | graph.addEdge('Data Structures', 'Algorithms'); 161 | graph.addEdge('CS2', 'Assembly Language'); 162 | graph.addEdge('CS2', 'Operating Systems'); 163 | graph.addEdge('CS1', 'CS2'); 164 | const result = graph.topSort('CS1'); 165 | expect(result.join(',')).to.equal('CS1,CS2,Operating Systems,Assembly Language,Data Structures,Algorithms'); 166 | }); 167 | }) -------------------------------------------------------------------------------- /advanced/graph/single_source_shortest_path/readme.md: -------------------------------------------------------------------------------- 1 | # 다익스트라 알고리즘 2 | 단일 출발지를 기준으로 각 정점까지의 최단 경로를 계산하는 알고리즘 3 | 4 | 간선의 가중치가 음의 값을 가지면 문제가 발생합니다. 5 | 이럴 때는 벨만-포드 알고리즘을 사용할 수 있습니다. 6 | 7 | 다익스트라 알고리즘의 기본적인 설명은 아래의 링크를 참고하면 좋을 것 같습니다. 8 | 9 | [다익스트라 동영상](https://www.youtube.com/watch?v=tzUJ7GE1qVs) 10 | 11 | ## 간선에 가중치가 있는 그래프 준비 12 | 우리는 일전에 간선이 없는 단순한 [그래프](../../../data_structures/graph) 클래스를 만들어 보았습니다. 13 | 해당 그래프를 상속받아서 가중치가 있는 그래프를 먼저 만들어 보겠습니다. 14 | 간단하게 만들기 위해 그래프는 무방향성 그래프로 만들 예정입니다. 15 | ```javascript 16 | export class Dijkstra extends Graph { 17 | constructor() { 18 | super(); 19 | } 20 | 21 | /** 22 | * 가중치 추가 23 | * @param data1 24 | * @param data2 25 | * @param weight 26 | */ 27 | addEdge(data1, data2, weight) { 28 | // 그래프에 저장된 각 정점의 인덱스를 준비합니다. 29 | const index1 = data1 instanceof Vertex ? this.findVertexIndex(data1.data) : this.findVertexIndex(data1); 30 | const index2 = data2 instanceof Vertex ? this.findVertexIndex(data2.data) : this.findVertexIndex(data2); 31 | // 그래프에 저장된 각 정점의 객체를 준비합니다. 32 | const obj1 = data1 instanceof Vertex ? data1 : this.findVertex(data1); 33 | const obj2 = data2 instanceof Vertex ? data2 : this.findVertex(data2); 34 | // 각 정점의 간선에 정점을 추가합니다. 35 | this.edges[index1].push(new Edge(obj2, weight)); 36 | this.edges[index2].push(new Edge(obj1, weight)); 37 | } 38 | } 39 | ``` 40 | 41 | ## 다익스트라 구현 42 | 먼저 우리는 아래와 같은 그래프를 기준으로 다익스트라 알고리즘을 구현할 예정입니다. 43 | ![그래프](./image_1.png) 44 | 45 | 다익스트라 알고리즘을 구현하기 전에 아래와 같은 정보가 필요합니다. 46 | * 시작 정점 47 | * 시작 정점으로 부터 모든 정점까지의 최단 경로 및 길이를 저장할 배열 48 | 49 | ### 시작 정점 받기 50 | 저는 shortest라는 함수를 하나 만들고 매개변수로 시작점을 받도록 구현하겠습니다. 51 | ```javascript 52 | class Dijkstra extends Graph { 53 | shortest(source) { 54 | let sourceIndex = this.findVertexIndex(source); 55 | } 56 | } 57 | ``` 58 | 59 | ### 정점간의 경로 정보 배열 생성 및 초기화 60 | 다음으로 입력받은 시작정점을 기준으로 그래프의 모든 정점간의 경로 정보를 담을 배열(shortest)을 생성하고 값을 초기화를 하겠습니다. 61 | 경로 정보는 2가지의 값을 가지고 있습니다. 62 | * 총 거리(distance) : 시작정점 -> 시작정점은 당연히 0으로 초기화하면 됩니다. 시작정점 -> 다른정점은 현재는 얼마나 걸리는지 알 수 없으므로 Infinity로 초기화합니다. 63 | * 경로(path) : 경로는 모두 빈 배열로 초기화합니다. 64 | 65 | ```javascript 66 | class Dijkstra extends Graph { 67 | ... 68 | shortest(source) { 69 | ... 70 | /** 71 | * 출발점 'source'로부터 각 정점까지의 최단 경로를 나타내는 배열 72 | * 출발점과 동일한 정점은 0, 나머지는 Infinity로 지정한다. 73 | */ 74 | const shortest = this.vertices.map((vertex, index) => { 75 | return { 76 | distance: index === sourceIndex ? 0 : Infinity, // 최단 경로의 거리 77 | path: [] // 최단 경로의 배열 78 | } 79 | }); 80 | } 81 | } 82 | ``` 83 | 84 | ### 방문한 인덱스와 방문할 인덱스 관리 전략 85 | 위에 링크로 걸린 동영상을 보시면 수행시간이 어떤 자료구조를 사용하느냐에 따라서 수행시간이 다른 것을 보실 수 있습니다. 86 | 하지만 저는 특별한 자료구조를 사용하지 않고 시작정점을 시작으로 반복문을 수행하면서 방문가능한 정점에 대한 거리를 비교하면서 그 때마다 다음 방문할 인덱스를 선별하도록 하겠습니다. 87 | 88 | 다만 중복으로 방문하는 것을 방지하기 위해 단순 일차원 배열을 사용하여 방문 인덱스를 관리하도록 하겠습니다. 89 | ```javascript 90 | class Dijkstra extends Graph { 91 | ... 92 | shortest(source) { 93 | ... 94 | const visitedIndex = []; // 방문한 정점의 인덱스를 저장할 배열 95 | let currIndex = sourceIndex; // 현재 방문할 정점을 저장할 변수 96 | while (currIndex) { // 방문 시작 97 | ... // TODO. 방문한 정점으로부터 갈 수 있는 정점을 대상으로 시작정점과의 최단 거리를 분석 98 | currIndex = 다음 방문할 정점 지정 99 | } 100 | } 101 | } 102 | ``` 103 | 104 | ### 방문한 정점으로부터 갈 수 있는 정점을 대상으로 시작정점과의 최단 거리를 분석 105 | 현재 방문한 정점(currIndex)로부터 갈 수 있는 정점들(toIndex)을 대상으로 시작정점(sourceIndex)과의 최단 거리는 아래의 두가지 경우 중 더 작은 경로를 의미합니다. 106 | 1. 시작정점(sourceIndex)에서 특정 정점(toIndex)까지의 이미 저장된 경로(shortest)의 거리 107 | 1. 시작정점(sourceIndex)에서 현재 방문 정점(currIndex)까지의 거리와 현재 방문 정점(currIndex)에서 방문 가능 정점(toIndex)까지의 거리를 더한 거리 108 | 109 | 또한 시작정점(sourceIndex)로부터 방금 구해진 정점(toIndex)까지의 거리 중 가장 작은 거리를 가진 인덱스가 다음 방문할 정점(currIndex)으로 지정합니다. 110 | 111 | 글로만 설명하려니 역시 한계가 있습니다. 코드를 살펴보도록 하겠습니다. 112 | ```javascript 113 | class Dijkstra extends Graph { 114 | ... 115 | shortest(source) { 116 | ... 117 | while (currIndex) { // 방문 시작 118 | visitedIndex[currIndex] = true; // 현재 정점을 방문 이력에 남깁니다. 119 | // 현재 정점에 연결된 정점에 대한 최단 거리 갱신 120 | let nextIndex = null; // 다음에 방문 예정인 인덱스를 임시로 저장할 변수를 준비합니다. 121 | // 현재 정점으로 부터 갈 수 있는 모든 정점을 탐색합니다. 122 | this.edges[currIndex].forEach( edge => { 123 | const toIndex = this.findVertexIndex(edge.dest); 124 | if (!visitedIndex[toIndex]) { // 이미 방문한 정점이면 무시하고 아직 방문전인 정점만 탐색합니다. 125 | /* 126 | 1. shortest[toIndex].distance = sourceIndex에서 toIndex까지의 현재 저장된 거리 127 | 2. shortest[currIndex].distance = sourceIndex에서 currIndex까지의 현재 저장된 거리 128 | 3. shortest[currIndex].distance + edge.weight = currIndex를 경유하여 toIndex까지 가는 거리 129 | 1번과 3번 항목 중 1번 항목이 더 크다면 3번항목으로 1번 값을 변경한다. 130 | */ 131 | if (shortest[toIndex].distance > shortest[currIndex].distance + edge.weight) { 132 | shortest[toIndex].distance = shortest[currIndex].distance + edge.weight; // 거리 갱신 133 | shortest[toIndex].path = shortest[currIndex].path.concat(toIndex); // 경로 갱신 134 | } 135 | /** 136 | * 다음에 방문 예정인 인덱스가 아직 지정되지 않았거나 137 | * 다음에 방문 예정인 인덱스까지의 거리보다 현재 분석한 정점까지의 거리가 더 짧다면 138 | * 다음에 방문 예정인 인덱스를 현재 분석한 정점으로 지정한다. 139 | */ 140 | nextIndex = !nextIndex || shortest[nextIndex].distance > shortest[toIndex].distance ? toIndex : nextIndex; 141 | } 142 | }); 143 | currIndex = nextIndex; // 다음에 방문 예정인 인덱스를 확정한다. 144 | } 145 | } 146 | } 147 | ``` 148 | 반복문을 수행하다보면 결국 다음 방문할 인덱스가 null이 될 것이고 이로서 탐색은 마무리됩니다. 149 | 150 | ### 실행 151 | 위의 그래프 그림을 참고하여 그래프 생성 및 정점 및 간선을 설정하면 아래와 같습니다. 152 | ```javascript 153 | const graph = new Dijkstra(); 154 | graph.addVertex('A'); // index : 0 155 | graph.addVertex('B'); // index : 1 156 | graph.addVertex('C'); // index : 2 157 | graph.addVertex('D'); // index : 3 158 | graph.addVertex('S'); // index : 4 159 | 160 | graph.addEdge('A', 'B', 2); 161 | graph.addEdge('A', 'C', 5); 162 | graph.addEdge('A', 'D', 1); 163 | graph.addEdge('B', 'C', 3); 164 | graph.addEdge('B', 'D', 6); 165 | graph.addEdge('C', 'D', 4); 166 | graph.addEdge('S', 'D', 8); 167 | graph.addEdge('S', 'C', 7); 168 | ``` 169 | 170 | 그리고 위에서 만든 shortest함수를 이용해서 호출을 해봅니다. 171 | 시작 정점은 S로 지정하면 아래와 같은 결과를 받을 수 있습니다. 172 | ```javascript 173 | const shortest = graph.shortest('S'); 174 | /* shortest 결과 175 | [ 176 | { distance: 9, path: [ 3, 0 ] }, 177 | { distance: 10, path: [ 2, 1 ] }, 178 | { distance: 7, path: [ 2 ] }, 179 | { distance: 8, path: [ 3 ] }, 180 | { distance: 0, path: [] } ] 181 | */ 182 | ``` 183 | shortest.path의 배열의 마지막 값이 최종 도착 정점이며, 각 장점까지의 최소 거리 및 경로는 아래와 같습니다. 184 | * 0 ('A') : 최단 거리 : 9, 경로 : 4 -> 3 -> 0 185 | * 1 ('B') : 최단 거리 : 10, 경로 : 4 -> 2 -> 1 186 | * 2 ('C') : 최단 거리 : 7, 경로 : 4 -> 2 187 | * 3 ('D') : 최단 거리 : 8, 경로 : 4 -> 3 188 | 189 | 모든 정점 중 최단 거리를 가진 정점을 찹아보도록 하겠습니다. 190 | ```javascript 191 | const result = shortest.filter(val => val.distance > 0).reduce((prev, curr) => prev.distance < curr.distance ? prev : curr); 192 | /* 결과 193 | { distance: 7, path: [ 2 ] } 194 | */ 195 | ``` -------------------------------------------------------------------------------- /data_structures/linkedList/readme.md: -------------------------------------------------------------------------------- 1 | # 연결 리스트 - Linked List 2 | 스택과 큐에서 이야기한 것 처럼 자바스크립트의 배열은 많은 기능 함수들을 가지고 있기 때문에 특별히 리스트 자료구조에 대한 포스트는 하지 않았습니다. 3 | 하지만 연결 리스트에 대한 학습을 하기 전에 먼저 리스트에 대한 간략한 요점과 일반적인 리스트(배열 리스트)와 연결 리스트의 비교를 먼저 해보도록 하겠습니다. 4 | 5 | ## 리스트 - List 6 | 리스트는 저장할 데이터가 많지 않을 때 유용하게 활용할 수 있습니다. 7 | 8 | 리스트는 아래와 같은 조건에서 유용합니다. 9 | * 항목을 검색할 필요가 없을 때 10 | * 일정한 순서로 항목을 집어넣을 필요가 없을 때 11 | 12 | 리스트는 아래와 같은 조건에서는 도움이 안됩니다. 13 | * 시간이 많이 소요되는 검색을 수행할 때 (조건이 복잡할 때) 14 | * 복잡한 정렬이 필요할 때(역시 조건이 복잡할 때) 15 | * 즉, 복잡한 자료구조와는 맞지 않습니다. 16 | 17 | 일반적으로 말하는 리스트는 내부적으로 순서(index 또는 sequence)가 있는 리스트로 배열 리스트(Array List)라고 합니다. 18 | 19 | 이 문서에서 살펴 볼 리스트는 연결 리스트로 배열 리스트와는 확실한 차이점이 있습니다. 20 | 21 | ## 리스트와 연결 리스트 22 | 자바스크립트의 배열도 결국 객체입니다. 또한 다른 언어에 비해 풍부한 기능 함수들을 가지고 있습니다. 23 | 이말은 결국 다른 언어의 배열에 비해 효율이 떨어진다는 이야기입니다. 24 | 25 | 배열을 사용하여 성능적인 문제가 발생된다면 대안으로 연결리스트를 사용할 수 있습니다. 26 | 그렇다고 무조건 연결 리스트가 좋은 건 아닙니다. 27 | 28 | * **리스트** : **빠른 읽기**, 느린 추가/삭제 (순차적 추가/삭제는 빠름) 29 | * **연결 리스트** : 느린 읽기, **빠른 추가/삭제** (데이터가 많을 수록 접근성이 떨어짐) 30 | 31 | ## 연결 리스트 - Linked List 32 | 연결 리스트는 노드(node)라는 객체가 모여 연결 리스트를 구성합니다. 33 | 각 노드는 객체 참조를 통해 리스트의 다른 노드와 연결을 유지합니다. 34 | 35 | 인덱스로 요소를 참조하는 배열 또는 리스트와 달리 연결 리스트는 다른 요소와의 관계를 통해 원하는 요소를 참조할 수 있습니다. 36 | 37 | 아무리 노드와 노드간의 참조로 구성이 된다고 하여도, 어디서부터 시작할 것인가 하는 문제가 있습니다. 38 | 이것은 일반적으로 헤더라고 표현되는 특별한 노드를 이용해서 연결 리스트의 시작점을 관리합니다. 39 | 40 | ## 구현 41 | 연결 리스트의 주요 구성요소는 노드와 연결리스트로 나눌 수 있습니다. 42 | 연결 리스트에는 리스트의 시작 노드를 참조하는 헤더와 다양한 기능함수들로 구성되어 있습니다. 43 | 노드에는 노드의 객체 또는 값을 저장하는 변수와 다음 노드의 정보를 가지고 있습니다. 44 | 45 | ### 노드 - Node 46 | 먼저 노드를 구현해 보겠습니다. 47 | ```javascript 48 | class Node { 49 | constructor(element) { 50 | this.element = element; // 저장할 객체 또는 값 51 | this.next = null; // 다음 연결될 참조 노드 52 | } 53 | } 54 | ``` 55 | 노드는 실제 값을 저장할 element라는 변수와 다음 노드를 참조할 next라는 변수로 구성되어 있습니다. 56 | 57 | ### 연결 리스트 - Linked List 58 | 다음으로 노드를 관리하는 연결 리스트를 구현해 보겠습니다. 59 | ```javascript 60 | class SimpleLinkedList { 61 | constructor() { 62 | this.head = null; 63 | this.length = 0; 64 | } 65 | } 66 | ``` 67 | 연결 리스트의 멤버변수는 노드의 시작점을 뜻하는 head와 삽입된 노드의 갯수를 뜻하는 length가 있습니다. 68 | 연결 리스트의 기본적인 메서드로는 노드 삽입, 노드 삭제, 노드 검색 등이 있을 것 입니다. 69 | 하나씩 구현해 보겠습니다. 70 | 71 | #### 노드 탐색 72 | 아래는 노드 탐색을 구현한 코드입니다. 단지 숫자로 전달된 매개변수를 이용해서 해당 순번의 노드를 반환하는 단순한 탐색을 구현해 보겠습니다. 73 | ```javascript 74 | class SimpleLinkedList { 75 | ... 76 | find(index) { 77 | if (typeof index === 'number') { 78 | let currIndex = 0; // 현재 탐색 중인 노드의 인덱스입니다. 79 | if (index < 0 || index > this.length) { // 찾으려는 인덱스가 삽입된 노드 길이보다 크다면 80 | return null; // null 반환합니다. 81 | } 82 | let target = this.head; // 최초 시작 노드를 설정합니다. 83 | while (currIndex !== index) { // 찾는 인덱스와 현재 인덱스가 같지 않으면 84 | target = target.next; // 다음 인덱스의 노드로 변경합니다. 85 | currIndex++; 86 | } 87 | return target; // 탐색한 노드를 반환합니다. 88 | } 89 | } 90 | } 91 | ``` 92 | 93 | #### 노드 삽입 94 | 노드를 삽입하는 메서드를 만들어 보려고 합니다. 95 | 자료의 삽입은 단순히 삽입만 발생하지 않습니다. 삽입 전에 어디에 삽입을 할지 조회하는 작업이 필요합니다. 96 | Java의 LinkedList는 삽입 메서드로 삽입할 자료와 인덱스 번호를 받아 해당 순번의 자료를 찾은 후 그 뒤에 자료를 삽입합니다. 97 | 혹, 어떤 경우는 인덱스 대신 검색할 자료를 넘겨 해당 자료를 탐색하기도 합니다. 98 | 99 | 위에서 이미 만든 탐색 메서드를 이용하여 삽입할 자료의 위치를 탐색 후 자료를 삽입하도록 하겠습니다. 100 | 101 | add() 메서드는 다음과 같은 매개변수를 받을 예정입니다. 102 | ##### 매개변수 103 | * item : 리스트에 추가할 값입니다. 104 | * index : 선택사항, 해당 인덱스 뒤에 자료를 삽입합니다. 105 | ```javascript 106 | class SimpleLinkedList { 107 | ... 108 | add(data, index) { // index가 지정되지 않으면 가장 마지막으로 노드를 삽입합니다. 109 | let target = this.head; 110 | let newNode = new Node(data); // 삽입할 노드를 생성합니다. 111 | if (index !== null && index !== undefined) { // 인덱스가 있다면 112 | target = this.find(index); //삽입할 위치를 탐색합니다. 113 | if (target !== null) { // 삽인된 노드가 하나라도 있다면 114 | newNode.next = target.next; // 삽입할 노드의 next에 검색한 노드의 next를 연결합니다. 115 | target.next = newNode; // 검색한 노드의 next는 삽입할 노드로 연결합니다. 116 | this.length++; // 삽입된 개수를 증가시킵니다. 117 | } else { // 해당 인덱스로 검색한 결과가 없으면 118 | // 아무 작업도 하지 않는다. 119 | } 120 | } else { // 인덱스가 없다면 121 | if (target) { // target이 null이 아니면, 즉, 리스트에 한개라도 삽입된 노드가 있다면 122 | while (target.next) { // target.next가 null일 때까지 탐색합니다. 즉, 가장 마지막 노드를 찾습니다. 123 | target = target.next; 124 | } 125 | target.next = newNode; // 마지막 노드의 next에 새로운 노드 newNode를 연결합니다. 126 | } else { // target이 null이면, 즉, 리스트에 삽입된 노드가 없다면 127 | this.head = newNode; // head에 newNode를 지정 128 | } 129 | this.length++; // 삽입된 개수를 증가시킵니다. 130 | } 131 | } 132 | ``` 133 | 코드의 라인수는 좀 있지만 복잡한 로직보다는 단순 분기처리가 많습니다. 134 | 결론적으로는 삽입할 위치 탐색 후 삽입이라는 절차에 충실합니다. 135 | 136 | #### 노드 삭제 137 | 삽입의 경우는 삽입할 위치의 앞 노드를 탐색 후 해당 노드의 next값을 교체하는 방식이었습니다. 138 | 삭제의 경우는 삭제할 인덱스의 앞의 노드를 탐색하여 next값을 삭제할 노드의 next로 교체하는 방식으로 동작합니다. 139 | ```javascript 140 | class SimpleLinkedList { 141 | ... 142 | remove(index) { 143 | let prevNode = this.find(index - 1); 144 | if (prevNode) { 145 | prevNode.next = prevNode.next.next; 146 | this.length--; 147 | } 148 | } 149 | } 150 | ``` 151 | 152 | 이렇게 노드의 탐색, 삽입, 삭제의 기능을 가진 단순한 연결 리스트를 만들어 보았습니다. 153 | 154 | 동일 저장소에 조금 다른 기능으로 구성한 LinkedList.js가 있으니 참고바랍니다. 155 | 156 | ## 양방향 연결 리스트 - DoublyLinkedList 157 | 위에서 살펴본 연결 리스트는 노드에 다음 노드를 가르키는 next만 존재했습니다. 그렇기 때문에 첫 번째 노드에서 마지막 노드까지는 쉽게 탐색할 수 있지만, 역방향으로의 탐색은 쉽지 않습니다. 158 | 노드에 이전 노드의 링크도 추가하여 관리하는 것이 바로 양방향 연결 리스트입니다. 159 | 160 | 먼저 노드 클래스에 next 외에 previous 변수를 추가하겠습니다. 161 | ```javascript 162 | class Node { 163 | constructor(element) { 164 | this.element = element; // 저장할 객체 또는 값 165 | this.next = null; // 다음 노드 링크 166 | this.prev = null; // 이전 노드 링크 167 | } 168 | } 169 | ``` 170 | 171 | ### 노드의 삽입 172 | 노드의 삽입에서는 이전노드, 추가될 노드, 다음노드에 각각 추가된 prev 값을 설정하도록 수정해야 합니다. 173 | ```javascript 174 | class DoublyLinkedList { 175 | ... 176 | add(data, index) { 177 | let prevNode = this.head; 178 | let newNode = new Node(data); 179 | if (index !== null && index !== undefined) { 180 | prevNode = this.find(index); 181 | if (prevNode !== null) { 182 | let nextNode = prevNode.next; 183 | newNode.next = nextNode; 184 | nextNode.prev = newNode; // 다음 노드의 prev에 추가될 노드를 지정합니다. 185 | prevNode.next = newNode; 186 | newNode.prev = prevNode; // 추가될 노드의 prev에 이전 노드를 지정합니다. 187 | this.length++; 188 | } 189 | } else { 190 | if (prevNode) { 191 | while (prevNode.next) { 192 | prevNode = prevNode.next; 193 | } 194 | prevNode.next = newNode; 195 | newNode.prev = prevNode; // 추가될 노드의 prev에 이전 노드를 지정합니다. 196 | } else { 197 | this.head = newNode; 198 | } 199 | this.length++; 200 | } 201 | } 202 | } 203 | ``` 204 | 205 | ### 노드의 삭제 206 | 노드의 삭제는 구조적으로는 간단하지만 코드적으로는 좀 길어집니다. 207 | ```javascript 208 | class DoublyLinkedList { 209 | ... 210 | remove(index) { 211 | let currNode = this.find(index); // 이번엔 삭제할 인덱스를 바로 탐색합니다. 212 | if (currNode) { 213 | if (currNode.prev) { // 이전 노드가 있다면 214 | currNode.prev.next = currNode.next; // 이전노드의 next를 다음노드로 연결합니다. 215 | } 216 | if (currNode.next) { // 다음 노드가 있다면 217 | currNode.next.prev = currNode.prev; // 다음노드의 prev를 이전노드로 연결합니다. 218 | } 219 | if (index === 0) { // 첫번째 노드를 삭제한다면 220 | this.head = currNode.next; // head값을 재설정합니다. 221 | } 222 | currNode.next = null; // 삭제할 노드의 연결정보를 삭제합니다. 223 | currNode.prev = null; // 삭제할 노드의 연결정보를 삭제합니다. 224 | this.length--; 225 | } 226 | } 227 | } 228 | ``` 229 | 230 | 이렇게 양방향 연결 리스트도 만들어 보았습니다. 231 | 혹시 이상한 점을 발견하신게 있으신가요? 232 | 가독성 있는 설명을 위해 현재 구성으로는 새로 추가할 노드를 연결 리스트의 맨 처음(head)으로 지정할 수 없습니다. 기회가 되시면 개인적으로 한번 코드를 수정해서 변경해보면 좋겠습니다. 233 | 234 | ## 그 외 연결 리스트 235 | 236 | ### 순환형 연결 리스트 237 | 순환형 연결 리스트는 마지막 노드의 next를 리스트의 head값으로 연결해서 뫼비우스의 띠처럼 끝없이 연결된 구조입니다. 238 | 양방향 리스트를 이용해서 역방향으로 탐색할 필요 없이 계속 다음 값을 참조하면 다시 앞쪽 노드를 탐색할 수 있습니다. 239 | 240 | ### 양방향 순환형 연결 리스트 241 | 양방향 순환형 연결 리스트는 예상하시는 것 처럼 양방향 연결 리스트의 첫 번째 노드의 이전 노드를 마지막 노드로, 마지막 노드의 다음 값은 첫 번째 노드로 연결하는 구조입니다. 242 | 예를 들면 TV 채널을 생각하시면 될 것 같습니다. -------------------------------------------------------------------------------- /data_structures/binarySearchTree/readme.md: -------------------------------------------------------------------------------- 1 | # 이진트리와 이진검색트리 - Binary Tree & Binary Search Tree 2 | 트리는 컴퓨터 과학에서 자주 등장하는 자료구조 중 하나입니다. 3 | 트리는 데이터를 계층적으로 저장하는 비연속 형식의 자료구조로, 파일시스템에 파일을 저장하거나 정렬된 데이터 리스트 등 계층적인 데이터를 저장할 때 사용됩니다. 4 | 5 | ## 트리 - Tree 6 | ### 트리란? 7 | 트리는 엣지와 노드로 연결된 집합입니다. OS의 폴더 구조, 회사의 조직도 등이 트리입니다. 8 | 9 | ### 트리 용어 10 | * 노드 node : 데이터의 상하위 계층을 나타내는 위치의 항목 11 | * 엣지 edge : 노드와 노드를 연결하는 선 또는 통로 12 | * 루트 root : 트리 최상위의 노드 13 | * 부모 노드 parent node : 아래의 한개 이상의 노드가 연결되어 있는 노드 14 | * 자식 노드 child node : 부모 노드 하위에 연결된 노드 15 | * 리프 노드 leaf node : 자식노드가 없는 노드 16 | * 레벨 level : 루트를 0레벨로 하위로 내려갈 수록 1레벨씩 증가하여 17 | 18 | ## 이진 트리 - Binary Tree 19 | 이진 트리는 자식 노드의 수가 두 개 이하인 트리를 의미합니다. 20 | 데이터 삽입, 검색, 삭제에 매우 효율적입니다. 21 | 하나의 노드는 왼쪽 자식 노드와 오른쪽 자식노드를 가질 수 있습니다. 22 | 23 | ## 이진 검색 트리 - Binary Search Tree 24 | 이진 검색 트리는 자신의 노드값보다 작은 값을 왼쪽 자식 노드에, 큰 값을 오른쪽 자식 노드에 저장을 합니다. 25 | 숫자 뿐만 아니라 단어 및 문자열도 저장할 수 있습니다. 26 | 27 | ### 구현 28 | 이진 검색 트리도 연결 리스트와 동일하게 Node라는 객체를 가지고 있습니다. 29 | 30 | ### 노드 31 | 노드는 값을 저장할 data와 자신보다 작은 값을 저장할 left노드와 큰 값을 저장할 right노드가 있습니다. 32 | 아래에 노드의 생성자 함수를 구현해보겠습니다. 33 | ```javascript 34 | class Node { 35 | constructor(data, left = null, right = null) { 36 | this.data = data; 37 | this.left = left; 38 | this.right = right; 39 | } 40 | } 41 | ``` 42 | 생성자 함수는 노드의 값을 지정하는 data와 현재 노드보다 작은 값이 저장될 left와 큰 값이 저장될 right를 받아 초기화 합니다. 43 | 44 | ### 트리 45 | 트리의 멤버변수는 루트 노드를 저장하는 한개의 변수가 있습니다. 46 | 아래 트리의 생성자 함수를 구현해보겠습니다. 47 | ```javascript 48 | class BST { 49 | constructor() { 50 | this.root = null; 51 | } 52 | } 53 | ``` 54 | 55 | ### 데이터 삽입 56 | 트리와 노드의 생성자 함수를 작성하였고 이제 데이터를 추가하는 기능을 구현하도록 하겠습니다. 57 | 데이터의 삽입은 아래와 같이 수행됩니다. 58 | 1. 루트를 current 노드로 설정 59 | 2. 삽입할 노드의 값이 current 노드의 값보다 작으면 새로운 current 노드를 current 노드의 왼쪽 자식으로, 값이 크면 새로운 current 노드를 current 노드의 오른쪽 자식으로 설정 60 | 3. 현재 current값이 null이면 새로운 노드를 current 노드의 값으로 삽입 후 루프를 종료 61 | 4. 현재 crruent값이 null이 아니라면 2-3번 항목을 반복해서 수행 62 | 63 | 위 내용을 참고하여 코드를 구현해보겠습니다. 64 | ```javascript 65 | class BST { 66 | ... 67 | insert(data) { 68 | const newNode = new Node(data); // 추가할 데이터의 노드를 생성합니다. 69 | if (this.root === null) { // 루트노드에 값이 없으면 루트로 지정하고 종료합니다. 70 | this.root = newNode; 71 | return; 72 | } 73 | let curr = this.root; // 현재 커서를 루트로 지정합니다. 74 | while (true) { // 반복합니다. 75 | if (data > curr.data) { // 추가할 데이터가 현재 노드값보다 크면 오른쪽을 탐색합니다. 76 | if (curr.right === null) { // 커서 오른쪽의 참조값이 null이면 77 | curr.right = newNode; // 추가할 노드를 커서의 right에 지정합니다. 78 | break; // 루프를 종료합니다. 79 | } else { // 커서 오른쪽의 참조값이 null이 아니면 80 | curr = curr.right; // 커서를 현재 커서의 오른쪽 노드로 지정합니다. 81 | } 82 | } else if (data < curr.data) { // 추가할 데이터가 현재 노드값보다 작으면 왼쪽을 탐색합니다. 83 | if (curr.left === null) { // 커서 왼쪽의 참조값이 null이면 84 | curr.left = newNode; // 추가할 노드를 커서의 left에 지정합니다. 85 | break; // 루프를 종료합니다. 86 | } else { // 커서 왼쪽의 참조값이 null이 아니면 87 | curr = curr.left; // 커서를 현재 커서의 왼쪽 노드로 지정합니다. 88 | } 89 | } else { 90 | break; // 현재 커서에 동일한 값이 이미 존재하므로 종료합니다. 91 | } 92 | } 93 | } 94 | } 95 | ``` 96 | insert 메서드는 재귀함수로 구현할 수 있고, 반복문으로 구현할 수 있습니다. 위 코드는 반복문으로 구현한 메서드이고, 재귀적으로 구현한 코드는 [BinarySearchTree.js](./BinarySearchTree.js)를 참조하시면 됩니다. 97 | 98 | ### 트리 탐색 99 | 이진 검색 트리는 3가지의 탐색 방법이 존재합니다. 100 | * **중위 탐색**inorder : 왼쪽 노드값, 자신의 노드값, 오른쪽 노드값 순서로 탐색합니다. (가장 왼쪽노드가 먼저 나옵니다) 101 | * **전위 탐색**preorder : 자신의 노드값, 왼쪽 노드값, 오른쪽 노드값 순서로 탐색합니다. (루트가 가장 먼저 나옵니다) 102 | * **후위 탐색**postorder : 왼쪽 노드값, 오른쪽 노드값, 자신의 노드값 순서로 탐색합니다. (가장 왼쪽이 먼저 나오고, 루트가 가장 늦게 나옵니다) 103 | 104 | 구현은 재귀호출로 깔끔하게 구현할 수 있습니다. 105 | 106 | #### 중위 탐색 107 | 먼저 중위탐색을 구현해보겠습니다. 108 | 중위 탐색은 루트를 현재 커서로 설정하고 왼쪽 노드값, 자신의 노드값, 오른쪽 노드값 순서로 탐색합니다. 109 | ```javascript 110 | class BST { 111 | ... 112 | inOrder(node = this.root) { 113 | if (node !== null) { 114 | this.inOrder(node.left); // 왼쪽 노드부터 탐색하여 출력합니다. 115 | console.log(node.data); // 왼쪽 노드를 다 출력 후 자신의 값을 출력합니다. 116 | this.inOrder(node.right); // 오른쪽 노드를 출력합니다. 117 | } 118 | } 119 | } 120 | ``` 121 | 122 | #### 전위 탐색 123 | 그다음 전위 탐색을 구현해보겠습니다. 124 | 전위 탐색은 루트를 현재 커서로 설정하고 자신의 노드값, 왼쪽 노드값, 오른쪽 노드값 순서로 탐색합니다. 125 | ```javascript 126 | class BST { 127 | ... 128 | preOrder(node = this.root) { 129 | if (node !== null) { 130 | console.log(node.data); // 자신의 값을 출력합니다. 131 | this.preOrder(node.left); // 왼쪽 노드부터 탐색하여 출력합니다. 132 | this.preOrder(node.right); // 오른쪽 노드를 출력합니다. 133 | } 134 | } 135 | } 136 | ``` 137 | 138 | #### 후위 탐색 139 | 마지막으로 후위 탐색을 구현해보겠습니다. 140 | 후위 탐색은 루트를 현재 커서로 설정하고 왼쪽 노드값, 오른쪽 노드값, 자신의 노드값 순서로 탐색합니다. 141 | ```javascript 142 | class BST { 143 | ... 144 | postOrder(node = this.root) { 145 | if (node !== null) { 146 | this.postOrder(node.left); // 왼쪽 노드부터 탐색하여 출력합니다. 147 | this.postOrder(node.right); // 오른쪽 노드를 출력합니다. 148 | console.log(node.data); // 자신의 값을 출력합니다. 149 | } 150 | } 151 | } 152 | ``` 153 | 154 | ### 데이터 검색 155 | 앞에서 살펴본 탐색방법은 다양한 용도로 사용할 수 있습니다. 156 | 그 중 몇가지에 대해서 알아 보겠습니다. 157 | 158 | #### 최소값과 최대값 검색 159 | 이진 검색 트리에서는 비교적 간단하게 최소값과 최대값을 검색할 수 있습니다. 160 | 최소값은 항상 왼쪽 자식에 있으므로, 왼쪽 자식이 null일 때까지 내려가면 됩니다. 161 | 반대로 최대값은 항상 오른쪽 자식에 잇으므로, 오른쪽 자식이 null일 떄까지 내려가면 됩니다. 162 | 163 | 최소값과 최대값을 구현해보도록 하겠습니다. 164 | #### 최소값 구하기 165 | 반복문을 이용해서 left가 null이 나올 때까지 내려가면 됩니다. 166 | 메소드의 매개변수 node는 지정된 값이 없다면 루트 노드부터 탐색을 하며 노드를 지정하면 해당 노드 하위로 탐색을 합니다. 167 | ```javascript 168 | class BST { 169 | ... 170 | getMin(node = this.root) { 171 | let curr = node; 172 | while (curr.left !== null) { 173 | curr = curr.left; 174 | } 175 | return curr.data; 176 | } 177 | } 178 | ``` 179 | 180 | #### 최대값 구하기 181 | 반복문을 이용해서 right가 null이 나올 때까지 내려가면 됩니다. 182 | 메소드의 매개변수 node는 지정된 값이 없다면 루트 노드부터 탐색을 하며 노드를 지정하면 해당 노드 하위로 탐색을 합니다. 183 | ```javascript 184 | class BST { 185 | ... 186 | getMax(node = this.root) { 187 | let curr = node; 188 | while (curr.right !== null) { 189 | curr = curr.right; 190 | } 191 | return curr.data; 192 | } 193 | } 194 | ``` 195 | 196 | #### 특정값 검색 197 | 위에서 작성한 최소값 최대값을 응용해서 이제는 특정값을 검색하는 기능을 구현해보겠습니다. 198 | 현재 커서의 값과 검색할 값이 같이면 해당 값을 출력하고, 크기를 비교해서 왼쪽으로 갈지 오른쪽으로 갈지를 정해주기만 하면 됩니다. 199 | ```javascript 200 | class BST { 201 | ... 202 | find(value) { 203 | let curr = this.root; 204 | while (curr && curr.data !== value) { 205 | if (curr.data > value) { 206 | curr = curr.left; 207 | } else if (curr.data < value) { 208 | curr = curr.right; 209 | } 210 | } 211 | return curr; 212 | } 213 | } 214 | ``` 215 | 216 | ### 데이터 삭제 217 | 이제 트리에 삽입된 노드를 삭제하는 기능을 구현해보겠습니다. 218 | 이것은 삽입이나 검색보다는 조금 복잡합니다. 219 | 삭제할 대상 노드와 이 노드의 자식 구성에 따라 방식이 조금씩 차이가 있습니다. 220 | 1. 먼저 삭제할 대상 노드을 검색합니다. 221 | 2. 대상 노드의 자식이 있는지 확인합니다. 222 | 3. 대상 노드의 자식이 없다면 대상 노드의 부모에 참조된 값을 null로 변경합니다. 223 | 4. 대상 노드의 자식이 한개라면 대상 노드의 부모에 참조된 값을 대상노드의 자식과 연결합니다. 224 | 5. 대상 노드의 자식이 두개 다 있다면 두 가지의 방법이 있습니다. 225 | * 왼쪽 자식 중 가장 큰 값 또는 오른쪽 자식 중 가장 작은값을 찾아서 대상 노드의 값으로 변경합니다. 226 | * 왼쪽 자식 중 가장 큰 값으로 검색했다는 가정하에 대상의 왼쪽 노드를 기준으로 가장 큰 값을 삭제합니다. 227 | * 삭제할 값이 null이 나올 떄까지 반복적으로 삭제합니다. 228 | 6. 대상 노드가 루트노드라면 BST에 참조된 루트 정보도 바꿔줍니다. 229 | 230 | 그렇지만 재귀적 호출을 이용하여 코드를 줄일 수 있습니다. 231 | 위 명세를 바탕으로 구현해보겠습니다. 232 | ```javascript 233 | class BST { 234 | ... 235 | remove(value) { // 루트의 변경을 처리하기 위해 삭제 메서드를 한번 감싼 메서드입니다. 236 | const node = this._remove(value, this.root); 237 | if (node) { 238 | this.root = node; 239 | } 240 | } 241 | 242 | _remove(value, node = this.root) { // 실제로 삭제 작업이 처리되는 메서드입니다. 243 | if (node === null) { 244 | return null; 245 | } 246 | if (node.data === value) { 247 | if (node.left === null && node.right === null) { 248 | // 자식이 둘다 null이면 249 | return null; //null을 반환한다. 250 | } else if (node.left === null && node.right !== null) { 251 | // 왼쪽 자식이 null이면 252 | return node.right; // 노드의 오른쪽 자식을 반환한다. 253 | } else if (node.left !== null && node.right === null) { 254 | // 오른쪽 자식이 null이면 255 | return node.left; // 노드의 왼쪽 자식을 연결한다. 256 | } else if (node.left !== null && node.right !== null) { 257 | // 두 자식이 모두 있으면 258 | const temp = this.getMax(node.left); // 왼쪽 자식 중 가장 큰 값을 조회한다. 259 | node.data = temp; // 노드의 값을 왼쪽 자식 중 가장 큰 값으로 변경한다. 260 | node.left = this._remove(temp, node.left); // 노드의 왼쪽 트리에서 왼쪽 자식 중 가장 큰 값을 삭제한다. 261 | return node; // 수정된 노드를 반환한다. 262 | } 263 | } else if (node.data < value) { 264 | node.right = this._remove(value, node.right); 265 | return node; 266 | } else if (node.data > value) { 267 | node.left = this._remove(value, node.left); 268 | return node; 269 | } 270 | } 271 | } 272 | ``` 273 | -------------------------------------------------------------------------------- /data_structures/graph/Graph.js: -------------------------------------------------------------------------------- 1 | export class Graph { 2 | constructor() { 3 | this.vertices = []; 4 | this.edges = []; 5 | } 6 | 7 | 8 | addVertex(data) { 9 | this.vertices.push(new Vertex(data)); 10 | this.edges.push([]); 11 | } 12 | findVertex(data) { 13 | return this.vertices.find( val => { 14 | return data instanceof Vertex ? val === data : val.data === data; 15 | }); 16 | } 17 | findVertexIndex(data) { 18 | return this.vertices.findIndex( val => { 19 | return data instanceof Vertex ? val === data : val.data === data; 20 | }); 21 | } 22 | findVertexByIndex(index) { 23 | return this.vertices[index]; 24 | } 25 | removeVertex(data) { 26 | const idx = this.findVertexIndex(data); 27 | this.vertices.splice(idx, 1); 28 | this.edges.splice(idx, 1); 29 | // TODO. Edge 삭제 30 | } 31 | 32 | addEdge(data1, data2) { 33 | // 그래프에 저장된 각 정점의 인덱스를 준비합니다. 34 | const index1 = data1 instanceof Vertex ? this.findVertexIndex(data1.data) : this.findVertexIndex(data1); 35 | const index2 = data2 instanceof Vertex ? this.findVertexIndex(data2.data) : this.findVertexIndex(data2); 36 | // 그래프에 저장된 각 정점의 객체를 준비합니다. 37 | const obj1 = data1 instanceof Vertex ? data1 : this.findVertex(data1); 38 | const obj2 = data2 instanceof Vertex ? data2 : this.findVertex(data2); 39 | // 각 정점의 간선에 정점을 추가합니다. 40 | this.edges[index1].push(new Edge(obj2)); 41 | this.edges[index2].push(new Edge(obj1)); 42 | } 43 | findEdge(data1, data2) { 44 | // 그래프에 저장된 data1의 정점의 인덱스를 준비합니다. 45 | const index1 = data1 instanceof Vertex ? this.findVertexIndex(data1.data) : this.findVertexIndex(data1); 46 | 47 | if (data2) { // data1과 data2가 간선으로 연결되어 있는지 검색합니다. 48 | // 그래프에 저장된 data2의 정점의 객체를 준비합니다. 49 | const obj2 = data2 instanceof Vertex ? data2 : this.findVertex(data2); 50 | return this.edges[index1].filter(edge => edge.dest === obj2); 51 | } else { // data1에 연결된 간선 모두를 검색합니다. 52 | return this.edges[index1]; 53 | } 54 | } 55 | removeEdge(data1, data2) { 56 | // 그래프에 저장된 data1의 정점의 인덱스를 준비합니다. 57 | const index1 = data1 instanceof Vertex ? this.findVertexIndex(data1.data) : this.findVertexIndex(data1); 58 | // 그래프에 저장된 data1의 정점의 객체를 준비합니다. 59 | const obj1 = data1 instanceof Vertex ? data1 : this.findVertex(data1); 60 | 61 | if (data2) { // data2가 있는 경우 62 | // 그래프에 저장된 data2의 정점의 인덱스를 준비합니다. 63 | const index2 = data2 instanceof Vertex ? this.findVertexIndex(data2.data) : this.findVertexIndex(data2); 64 | // 그래프에 저장된 data2의 정점의 객체를 준비합니다. 65 | const obj2 = data2 instanceof Vertex ? data2 : this.findVertex(data2); 66 | // data2의 edges 배열에 있는 data1의 인덱스 67 | const removeIndex1 = this.edges[index2].findIndex(edge => edge.dest === obj1); 68 | // data1의 edges 배열에 있는 data2의 인덱스 69 | const removeIndex2 = this.edges[index1].findIndex(edge => edge.dest === obj2); 70 | 71 | // data2의 edges 배열에서 data1의 인덱스 삭제 72 | this.edges[index1].splice(removeIndex2, 1); 73 | // data1의 edges 배열에서 data2의 인덱스 삭제 74 | this.edges[index2].splice(removeIndex1, 1); 75 | } else { // data2가 없는 경우 76 | this.edges[index1] 77 | .map(edge => edge.dest) // 아래의 재귀 호출로 인해 배열이 순회하는 중 삭제작업으로 인해 배열이 감소하게 됩니다. 그렇기 때문에 삭제 대상이 누락될 수 있기 때문에 삭제 대상을 먼저 배열로 반환합니다. 78 | .forEach(dest => { // 삭제할 정점 정보를 가지고 삭제 메서드를 재귀 호출합니다. 79 | this.removeEdge(dest, obj1); // data1의 엣지 인접리스트에 있는 모든 정점과 data1 정점과의 간선을 제거한다. 80 | }); 81 | } 82 | } 83 | toString() { 84 | let result = ''; 85 | for (let key of Object.keys(this.edges)) { 86 | let to = key + ' ->'; 87 | this.edges[key].forEach( val => to += ' ' + val.dest); 88 | result += to + '\n'; 89 | } 90 | return result; 91 | } 92 | /** 93 | * 깊이 우선 탐색 94 | * @param data : 방문 및 인접 리스트를 탐색할 정점의 값 또는 정점 객체입니다. 95 | * @param fn : 탐색만 하고 아무것도 안 할 것이 아니기에 탐색 시 처리할 작업을 정의할 수 있습니다. 96 | * @param visited : 이번 탐색을 통해 방문한 정점을 기록하는 배열입니다. 기본값은 빈 객체로 지정합니다. 97 | */ 98 | dfs(data, fn, visited = {}) { 99 | const index = this.findVertexIndex(data); // 정점의 인덱스를 얻어옵니다. 100 | const vertex = this.findVertex(data); // 정점의 객체를 얻어옵니다. 101 | visited[index] = true; // 방문한 정점을 기록합니다. 102 | fn(vertex); // 탐색 시 진행할 작업을 처리합니다. 103 | this.edges[index].forEach( v => { // 정점의 인접 리스트를 재귀적으로 탐색합니다. 104 | const innerIndex = this.findVertexIndex(v.dest); // 인접한 정점의 인덱스를 얻어옵니다. 105 | if (!visited[innerIndex]) { // 방문한 정점이 아니라면 106 | this.dfs(v.dest.data, fn, visited); // 해당 정점을 기준으로 인접 리스트 탐색을 시작합니다. 107 | } 108 | }); 109 | } 110 | 111 | /** 112 | * 너비 우선 탐색 113 | * @param data : 방문 및 인접 리스트를 탐색할 정점의 값 또는 정점 객체입니다. 114 | * @param fn : 탐색만 하고 아무것도 안 할 것이 아니기에 탐색 시 처리할 작업을 정의할 수 있습니다. 115 | * @param visited : 이번 탐색을 통해 방문한 정점을 기록하는 배열입니다. 기본값은 빈 객체로 지정합니다. 116 | */ 117 | bfs(data, fn, visited = []) { 118 | const vertex = this.findVertex(data); // 정점의 객체를 얻어옵니다. 119 | const queue = []; // 탐색할 정점을 담을 대기 큐 120 | queue.push(vertex); // 첫 번째 정점을 큐에 담습니다. 121 | while(queue.length > 0) { // 더 이상 방문할 정점이 없을 때까지 반복합니다. 122 | const currVertex = queue.shift(); // 큐에서 탐색할 정점을 하나 꺼냅니다. 123 | const index = this.findVertexIndex(currVertex); // 정점의 인덱스를 얻어옵니다. 124 | if (!visited[index]) { // 방문한 정점이 아니면 125 | visited[index] = true; // 방문 기록을 남깁니다. 126 | fn(currVertex); // 방문 후 처리할 작업을 호출합니다. 127 | this.edges[index].forEach( val => { // 정점의 인접한 정점을 큐에 담습니다. 128 | queue.push(val.dest); 129 | }) 130 | } 131 | } 132 | } 133 | 134 | /** 135 | * 특정 정점을 기준으로 갈 수 있는 정점을 반환한다. 136 | * @param data : 연결된 간선 정보를 탐색할 시작 정점 137 | * @returns {Array} 138 | */ 139 | edgeTo(data) { 140 | const vertex = this.findVertex(data); // 정점의 객체를 얻어옵니다. 141 | const edgeTo = []; // 간정 정보를 기록할 배열입니다. 142 | const queue = []; // 탐색할 정점을 담을 대기 큐 143 | const visited = []; // 이미 방문한 정점을 기록할 배열입니다. 144 | queue.push(vertex); // 시작 정점을 큐에 담습니다. 145 | while(queue.length > 0) { // 더 이상 방문할 정점이 없을 때까지 반복합니다. 146 | const currVertex = queue.shift(); // 방문할 정점을 하나 꺼냅니다. 147 | const index = this.findVertexIndex(currVertex); // 정점의 인덱스를 얻어옵니다. 148 | this.edges[index].forEach( val => { // 정점에서 갈 수 있는 정점을 확인합니다. 149 | const innerIndex = this.findVertexIndex(val.dest); // 인접한 도착 가능한 정점의 인덱스를 얻어옵니다. 150 | if (!visited[innerIndex]) { // 아래는 기존 bfs와 다른 방문기록 방식입니다 151 | edgeTo[innerIndex] = currVertex; // 이전 정점(currVertex)에서 현재 정점으로 왔다는 정보를 edgeTo 배열에 등록합니다. 152 | visited[innerIndex] = true; // 위 정보를 기록 후 방문기록에 추가합니다. 153 | queue.push(val.dest); // 방문할 큐에 담습니다. 154 | } 155 | }) 156 | } 157 | return edgeTo; 158 | } 159 | 160 | /** 161 | * 너비 우선 탐색을 통해 얻은 간선 정보를 바탕으로 최단 거리를 반환합니다. 162 | * @param vertexSource : 출발 정점 163 | * @param vertexDestination : 도착 정점 164 | * @returns {*} 165 | */ 166 | pathFromTo(vertexSource, vertexDestination) { 167 | const edgeTo = this.edgeTo(vertexSource); // 시작 정점에서 갈 수 있는 정점을 반환합니다. 168 | const path = []; 169 | /* 170 | * 도착지점을 시작으로 시작 지점이 나올 때까지 edgeTo 배열을 추적합니다. 171 | */ 172 | for(let i = vertexDestination; i !== vertexSource; i = edgeTo[this.findVertexIndex(i)].data) { 173 | path.push(i); // 왔던 경로는 path 배열에 기록합니다. 174 | } 175 | path.push(vertexSource); // 마지막으로 시작 지점을 배열에 담습니다. 176 | return path.reverse(); // 해당 배열을 역으로 정렬합니다. 177 | } 178 | 179 | /** 180 | * 위상 정렬 팩토리 메서드 181 | * @param data : 위상 정렬을 시작할 정점 182 | * @returns {*} 183 | */ 184 | topSort(data) { 185 | const vertex = this.findVertex(data); // 정점의 객체를 얻어옵니다. 186 | const stack = []; // 정렬된 정점을 담을 스택용 배열입니다. 187 | this._topSort(vertex, val => { // 실제 위상 정렬을 수행하는 메서드를 호출하고, 콜백 함수를 정의합니다. 188 | stack.push(val); 189 | }); 190 | return stack.reverse(); // 배열을 스택형태로 한번에 꺼내기 위해 역순으로 반환합니다. 191 | } 192 | 193 | /** 194 | * 위상 정렬 195 | * @param vertex : 탐색할 정점 196 | * @param fn : 콜백 함수 197 | * @param visited : 방문한 정점의 배열 198 | */ 199 | _topSort(vertex, fn, visited = []) { 200 | const index = this.findVertexIndex(vertex); // 정점의 인덱스를 얻어옵니다. 201 | visited[index] = true; // 방문을 기록합니다. 202 | this.edges[index].forEach( v => { // 해당 정점에서 갈 수 있는 정점을 순회합니다. 203 | const innerIndex = this.findVertexIndex(v.dest); // 인접한 정점의 인덱스를 얻어옵니다. 204 | if (!visited[innerIndex]) { //방문하지 않은 정점이라면 깊이 우선 탐색을 진행합니다. 205 | this._topSort(v.dest, fn, visited); 206 | } 207 | }); 208 | fn(vertex.data); // 콜백 함수를 실행합니다. 209 | } 210 | } 211 | 212 | export class Vertex { 213 | constructor(data) { 214 | this.data = data; 215 | } 216 | } 217 | 218 | export class Edge { 219 | constructor(dest, weight = 0) { 220 | this.dest = dest; 221 | this.weight = weight; 222 | } 223 | } -------------------------------------------------------------------------------- /data_structures/graph/readme.md: -------------------------------------------------------------------------------- 1 | # 그래프 - Graph 2 | 그래프는 정점과 간선으로 이루어져 있는 자료구조입니다. 3 | 우리가 흔히 보는 지도 또한 일종의 그래프라고 할 수 있습니다. 목적지가 정점이라면 그 목적지까지 연결된 도로는 간선입니다. 4 | 간선은 정점과 정점의 하나의 쌍으로 정의합니다. 정점은 무게 또는 비용을 포함할 수 있습니다. 5 | 간선에서 정점의 순서를 따지는 그래프를 방향성 그래프라고 합니다. 방향성 그래프는 화살표로 간선을 표시합니다. 6 | 방향성이 없는 그래프느를 무방향성 그래프 또는 그래프라고 합니다. 7 | 8 | #### 정점 9 | * 정점, 노드node, 버텍스vertex라고 표현 10 | * 무게 또는 비용을 포함할 수 있음 11 | 12 | #### 간선 13 | * 간선, 엣지edge, 아크arc라고 표현 14 | * 정점과 정점의 하나의 쌍으로 표현 15 | * 정점에 순서가 있는 것을 방향성 그래프라고 함 16 | * 정점에 순서가 없는 것을 무방향성 그래프 또는 그래프라고 함 17 | 18 | #### 경로 19 | * 모든 정점이 간선으로 연결되어 있는 묶음 20 | * 경로의 길이는 경로의 첫 번째 정점에서 마지막 정점까지의 간선 수 21 | 22 | #### 사이클 23 | * 첫 번째 정점에서 마지막 정점으로 도달하는 하나 이상의 간선으로 이루어졌으며 경로가 같은 상황 24 | * 단순 사이클 : 간선이나 정점이 반복되지 않는 경로 25 | * 일반 사이클 : 첫 정점과 마지막 정점을 제외한 다른 정점이 반복되는 경로 26 | 27 | #### 강한 연결 28 | * 한 정점이 다른 정점과 연결되어 있고, 다른 정점도 한 정점과 연결되어 있는 경우 29 | 30 | ## 생활 속 그래프 31 | * 도로 : 정점은 교차로, 간선은 도로 32 | 1. 간선에 가중치를 추가하여 속도 제한 및 도로의 차선 수를 표현할 수 있음 33 | 2. 그래프를 이용해 교통 체증 을 최소화할 수 있는 최적의 경로와 도로를 결정할 수 있음 34 | * 이동수단 시스템(항공사 비행 시스템) : 정점은 공항, 간선은 비행 경로 35 | 1. 간선에 가중치를 추가하여 공항 간의 비행 비용이나 공항간의 거리를 표현 36 | * 근거리 통신망 37 | * 인터넷과 같은 네트워크 38 | * 소비자 시장 39 | 40 | > 트리도 그래프의 일종입니다 41 | 42 | ## 구현 43 | 그래프 자체를 객체로 표현하기 보다는 정점과 간선을 객체로 표현하는 방식이 효율성면에서 더 좋습니다. 44 | 45 | ### 정점 46 | 이번에 만들어 볼 그래프에서의 정점은 단순히 식별자의 개념만 가지고 있습니다. 47 | 그래서 아래와 같이 간단하게 표현할 수 있습니다. 48 | ```javascript 49 | class Vertex { 50 | constructor(data) { 51 | this.data = data; 52 | } 53 | } 54 | ``` 55 | 56 | ### 간선 57 | 실제 그래프의 구조를 표현하는 것은 간선의 역할입니다. 58 | 간선은 인접 행렬과 인접 리스트를 이용하는 두가지 방법이 있습니다. 59 | 인접 행렬은 구현은 간단하지만 공간의 효율성이 떨어집니다. 60 | 이번에는 인접 행렬이 아닌 인접 리스트를 이용해서 구현하도록 하겠습니다. 61 | 인접 리스트의 구성도 어렵진 않습니다. 62 | from, to를 생각하면 좋을 것 같습니다. from을 출발지로 생각한다면 갈 수 있는 곳은 복수일 수 있습니다. 63 | from을 리스트의 키로 생각한다면 to는 리스트의 값으로 배열로 구성됩니다. 64 | 65 | 먼저 코드부터 확인하겠습니다. 66 | ```javascript 67 | class Edge { 68 | constructor(dest, weight = 0) { 69 | this.dest = dest; 70 | this.weight = weight; 71 | } 72 | } 73 | ``` 74 | 위에 간선을 설명할 때는 분명 출발지와 목적지의 두가지 요소가 있었는데 실제 구현한 코드에는 목적지만 있습니다. 75 | 구현하는 방법은 개인마다 조금씩 차이가 있습니다만, 저는 출발지는 정점 배열의 인덱스와 동일하게 관리를 하려고 합니다. 76 | 77 | 실제 이번 예제에서 사용하진 않지만 가중치를 의미하는 weight 변수도 추가해 보았습니다. 78 | 79 | ### 그래프 80 | 이제 그래프를 구현할 차례입니다. 81 | 그래프의 요소는 정점과 간선을 저장할 배열로 구성할 예정입니다. 82 | 먼저 생성자 함수를 구성해보겠습니다. 83 | ```javascript 84 | class Graph { 85 | constructor() { 86 | this.vertices = []; 87 | this.edges = []; 88 | } 89 | } 90 | ``` 91 | 이제 객체들의 구성은 완료되었습니다. 이제 실제 작동을 위한 기능을 하나씩 구현해보도록 하겠습니다. 92 | 93 | ### 정점 추가 94 | 가장 먼저 구현할 기능은 바로 정점을 추가하는 기능입니다. 95 | 정점의 추가는 간단합니다. 96 | 1. 그래프 객체의 vertices 배열에 정점 객체를 생성해서 추가합니다. 97 | 1. 그래프 객체의 edges 배열에 빈 배열 객체를 추가합니다. 98 | ```javascript 99 | class Graph { 100 | ... 101 | addVertex(data) { 102 | this.vertices.push(new Vertex(data)); // 정점 추가 103 | this.edges.push([]); // 간선의 출발지 추가 104 | } 105 | } 106 | ``` 107 | verticesedges의 인덱스는 동일한 정점의 인덱스를 가지고 있다고 보면 됩니다. 108 | 109 | ### 정점 탐색 110 | 이번에는 정점을 탐색하는 기능을 추가해보겠습니다. 111 | 정점 탐색은 vertices에 추가된 배열을 탐색하여 정점 객체의 data값을 비교해서 반환할 생각합니다. 112 | ```javascript 113 | class Graph { 114 | ... 115 | findVertex(data) { 116 | return this.vertices.find( val => val.data === data); 117 | } 118 | } 119 | ``` 120 | 정점 객체 자체말고 정점의 인덱스를 탐색하는 기능도 한번 추가해보겠습니다. 121 | ```javascript 122 | class Graph { 123 | ... 124 | findVertexIndex(data) { 125 | return this.vertices.findIndex( val => val.data === data); 126 | } 127 | } 128 | ``` 129 | 정점 탐색은 자바스크립트 배열의 강력한 내장 함수를 이용해서 간단하게 구현하였습니다. 130 | 131 | ### 정점 삭제 132 | 이번에는 정점의 삭제를 구현해보겠습니다. 133 | 삭제에는 아래와 같은 작업이 필요합니다. 134 | 1. 삭제할 정점의 인덱스 조회 135 | 1. vertices 배열에서 해당 인덱스를 제거합니다. 136 | 1. edges 배열에서 해당 인덱스를 제거합니다. 137 | 이미 우리는 정점의 인덱스를 조회하는 메서드를 만들어서 작업이 간단할 것 같습니다. 138 | 코드로 구현해보겠습니다. 139 | ```javascript 140 | class Graph { 141 | ... 142 | removeVertex(data) { 143 | const idx = this.findVertexIndex(data); // 삭제할 정점의 인덱스를 조회 144 | this.vertices.splice(idx, 1); //정점 삭제 145 | this.edges.splice(idx, 1); // 간선 삭제 146 | } 147 | } 148 | ``` 149 | 150 | ### 간선 추가 151 | 위에서 우리는 정점의 추가, 조회, 삭제 기능을 구현을 했습니다. 이번에는 간선에 대한 기능을 구현해보겠습니다. 152 | 첫번 째로 간선의 추가를 구현해보겠습니다. 153 | 154 | 간선은 두 개의 정점으로 구성 됩니다. 155 | 현재 우리가 만드는 그래프는 무방향 그래프로 간선 A와 B는 edges 배열에 A배열에 B를 추가하고, B배열에 A를 추가합니다. 156 | 각 A와 B배열의 인덱스는 vertices 배열의 인덱스와 동일하기 때문에 우리가 이미 구현한 findVertexIndex 메서드를 이용해서 인덱스를 구할 수 있습니다. 157 | 158 | 저는 2가지의 경우로 구성을 하려고 합니다. 159 | 매개변수로 정점의 값을 넘기거나, 정점 객체 자체를 넘길 수도 있었으면 합니다. 160 | 코드를 구현해보겠습니다. 161 | ```javascript 162 | class Graph { 163 | ... 164 | addEdge(data1, data2) { 165 | // 그래프에 저장된 각 정점의 인덱스를 준비합니다. 166 | const index1 = data1 instanceof Vertex ? this.findVertexIndex(data1.data) : this.findVertexIndex(data1); 167 | const index2 = data2 instanceof Vertex ? this.findVertexIndex(data2.data) : this.findVertexIndex(data2); 168 | // 그래프에 저장된 각 정점의 객체를 준비합니다. 169 | const obj1 = data1 instanceof Vertex ? data1 : this.findVertex(data1); 170 | const obj2 = data2 instanceof Vertex ? data2 : this.findVertex(data2); 171 | // 각 정점의 간선에 정점을 추가합니다. 172 | this.edges[index1].push(new Edge(obj2)); 173 | this.edges[index2].push(new Edge(obj1)); 174 | } 175 | } 176 | ``` 177 | 코드가 여러줄이긴 하지만 매우 간단하고 직관적인 구성을 가지고 있습니다. 178 | 179 | ### 간선 검색 180 | 간선 추가 기능을 구현했으니 간선을 검색할 수 있는 기능도 구현해보겠습니다. 181 | 간선 검색의 기능은 아래와 같이 정의하려고 합니다. 182 | 1. 하나의 정점과 연결된 모든 간선 검색 183 | 2. 두개의 정점이 연결되었는지 검색 184 | 185 | 검색 메서드의 매개변수는 최대 2개로 첫 번째 매개변수만 있으면 위의 1번 기능으로 검색하고, 186 | 두 번째 매개변수도 있다면 위의 2번의 기능으로 검색하고자 합니다. 187 | 188 | 또한 추가와 동일하게 매개변수로 받는 정점은 값일수도 있고 객체 자체일 수도 있어야 합니다. 189 | 코드로 구현해보겠습니다. 190 | ```javascript 191 | findEdge(data1, data2) { 192 | // 그래프에 저장된 data1의 정점의 인덱스를 준비합니다. 193 | const index1 = data1 instanceof Vertex ? this.findVertexIndex(data1.data) : this.findVertexIndex(data1); 194 | 195 | if (data2) { // data1과 data2가 간선으로 연결되어 있는지 검색합니다. 196 | // 그래프에 저장된 data2의 정점의 객체를 준비합니다. 197 | const obj2 = data2 instanceof Vertex ? data2 : this.findVertex(data2); 198 | return this.edges[index1].filter(edge => edge.dest === obj2); 199 | } else { // data1에 연결된 간선 모두를 검색합니다. 200 | return this.edges[index1]; 201 | } 202 | } 203 | ``` 204 | 205 | ### 간선 제거 206 | 간선에 대한 마지막 기능으로 제거가 남았습니다. 207 | 208 | 간선 제거는 일반적으로 두개의 정점을 받아서 두 정점에 연결된 간선을 제거합니다. 209 | 하지만 저희는 하나의 정점만 받아도 작동하도록 구현해볼까 합니다. 210 | 단 정점이 하나인 경우는 해당 정점에 연결된 모든 간선을 제거하도록 합시다. 211 | 또한 추가 및 검색과 동일하게 매개변수로 받는 정점은 값일 수도 있고 객체 자체일 수도 있어야 합니다. 212 | 213 | 조금 복잡하긴 합니다만 구현해보겠습니다. 214 | ```javascript 215 | class Graph { 216 | ... 217 | removeEdge(data1, data2) { 218 | // 그래프에 저장된 data1의 정점의 인덱스를 준비합니다. 219 | const index1 = data1 instanceof Vertex ? this.findVertexIndex(data1.data) : this.findVertexIndex(data1); 220 | // 그래프에 저장된 data1의 정점의 객체를 준비합니다. 221 | const obj1 = data1 instanceof Vertex ? data1 : this.findVertex(data1); 222 | 223 | if (data2) { // data2가 있는 경우 224 | // 그래프에 저장된 data2의 정점의 인덱스를 준비합니다. 225 | const index2 = data2 instanceof Vertex ? this.findVertexIndex(data2.data) : this.findVertexIndex(data2); 226 | // 그래프에 저장된 data2의 정점의 객체를 준비합니다. 227 | const obj2 = data2 instanceof Vertex ? data2 : this.findVertex(data2); 228 | // data2의 edges 배열에 있는 data1의 인덱스 229 | const removeIndex1 = this.edges[index2].findIndex(edge => edge.dest === obj1); 230 | // data1의 edges 배열에 있는 data2의 인덱스 231 | const removeIndex2 = this.edges[index1].findIndex(edge => edge.dest === obj2); 232 | 233 | // data2의 edges 배열에서 data1의 인덱스 삭제 234 | this.edges[index1].splice(removeIndex2, 1); 235 | // data1의 edges 배열에서 data2의 인덱스 삭제 236 | this.edges[index2].splice(removeIndex1, 1); 237 | } else { // data2가 없는 경우 238 | this.edges[index1] 239 | .map(edge => edge.dest) // 아래의 재귀 호출로 인해 배열이 순회하는 동안 삭제작업으로 인해 배열이 감소하게 됩니다. 그렇기 때문에 삭제 대상이 누락되기 때문에 삭제 대상을 먼저 배열로 반환합니다. 240 | .forEach(dest => { // 반환된 삭제대상 항목을 다시 재귀하여 241 | this.removeEdge(dest, obj1); // data1의 엣지 인접리스트에 있는 모든 정점과 data1 정점과의 간선을 제거한다. 242 | }); 243 | } 244 | } 245 | } 246 | ``` 247 | 사실 주석이 많아서 코드가 복잡해 보이지만 그렇게 복잡하진 않은 것 같습니다. 248 | 249 | ## 그래프 순회 250 | 그래프 순회(traversal)란 그래프의 모든 정점들을 체계적으로 방문하는 것입니다. 그래프 순회는 아래의 문제들을 푸는데 유용합니다. 251 | * 주어진 그래프가 연결 그래프인지를 판정 252 | * 연결 성분을 구하는 문제 253 | * 사이클의 존재 여부를 판정하는 문제 254 | 255 | 그래프를 순회하는 방법으로 깊이 우선 검색(depth first search)과 너비 우선 검색(breadth first search)이 있습니다. 256 | 257 | ### 깊이 우선 검색 ( DFS - Depth First Search ) 258 | 먼저 깊이 우선 검색에 대해 알아보겠습니다. 259 | 깊이 우선 검색은 정점을 방문할 때 갈 수 있는 데까지 우선 가보다가 더 이상 진행할 수 없으면 길을 거슬러 올라가면서 아직 가보지 않은 길이 있으면 그 길을 따라 또 갈 수 있는 데까지 가 보는 방법입니다. 260 | 261 | 이제 구현하는 방법을 알아보겠습니다. 262 | 1. 탐색을 완료한 정점을 기록할 변수가 필요합니다. 263 | 2. 재귀적으로 정점의 인접 리스트에서 방문하지 않았던 다른 정점을 방문합니다. 264 | 265 | 함수의 재귀적 호출은 결국 자바스크립트의 큐 스택을 이용하여 처리가 됩니다. 즉, 스택 자료구조를 활용해서 처리를 한다고 생각하시면 됩니다. 266 | 267 | 코드를 구현해보도록 하겠습니다. 268 | ```javascript 269 | class Graph { 270 | ... 271 | /** 272 | * 깊이 우선 탐색 273 | * @param data : 방문 및 인접 리스트를 탐색할 정점의 값 또는 정점 객체입니다. 274 | * @param fn : 탐색만 하고 아무것도 안 할 것이 아니기에 탐색 시 처리할 작업을 정의할 수 있습니다. 275 | * @param visited : 이번 탐색을 통해 방문한 정점을 기록하는 배열입니다. 기본값은 빈 객체로 지정합니다. 276 | */ 277 | dfs(data, fn, visited = {}) { 278 | const index = this.findVertexIndex(data); // 정점의 인덱스를 얻어옵니다. 279 | const vertex = this.findVertex(data); // 정점의 객체를 얻어옵니다. 280 | visited[index] = true; // 방문한 정점을 기록합니다. 281 | fn(vertex); // 탐색 시 진행할 작업을 처리합니다. 282 | this.edges[index].forEach( v => { // 정점의 인접 리스트를 재귀적으로 탐색합니다. 283 | const innerIndex = this.findVertexIndex(v.dest); // 인접한 정점의 인덱스를 얻어옵니다. 284 | if (!visited[innerIndex]) { // 방문한 정점이 아니라면 285 | this.dfs(v.dest.data, fn, visited); // 해당 정점v를 기준으로 인접 리스트 탐색을 시작합니다. 286 | } 287 | }); 288 | } 289 | } 290 | ``` 291 | 292 | ### 너비 우선 검색 ( BFS - Breadth First Search ) 293 | 너비 우선 검색은 첫 번째 정점에서 인접한 정점을 모두 탐색 후 각 인접한 정점에 인접한 정점을 탐색하면서 더 이상 탐색할 정점이 없을 때까지 반복합니다. 294 | 굳이 비교 하자면 깊이 우선 검색이 스택을 기반한 검색이라면 너비 우선 검색은 큐를 기반으로 검색합니다. 295 | 296 | 1. 현재 정점을 방문 후 방문하지 않은 인접한 정점을 다음에 방문할 리스트(큐)에 추가합니다. 297 | 2. 더이상 방문할 정점이 없을 때까지 방문할 리스트의 맨 앞의 정점을 꺼내서 1번부터 반복합니다. 298 | ```javascript 299 | class Graph { 300 | ... 301 | /** 302 | * 너비 우선 탐색 303 | * @param data : 방문 및 인접 리스트를 탐색할 정점의 값 또는 정점 객체입니다. 304 | * @param fn : 탐색만 하고 아무것도 안 할 것이 아니기에 탐색 시 처리할 작업을 정의할 수 있습니다. 305 | * @param visited : 이번 탐색을 통해 방문한 정점을 기록하는 배열입니다. 기본값은 빈 객체로 지정합니다. 306 | */ 307 | bfs(data, fn, visited = []) { 308 | const vertex = this.findVertex(data); // 정점의 객체를 얻어옵니다. 309 | const queue = []; // 탐색할 정점을 담을 대기 큐 310 | queue.push(vertex); // 첫 번째 정점을 큐에 담습니다. 311 | while(queue.length > 0) { // 큐에 더이상 탐색할 정점이 없을 때까지 반복합니다. 312 | const currVertex = queue.shift(); // 큐에서 탐색할 정점을 하나 꺼냅니다. 313 | const index = this.findVertexIndex(currVertex); // 정점의 인덱스를 얻어옵니다. 314 | if (!visited[index]) { // 방문한 정점이 아니면 315 | visited[index] = true; // 방문 기록을 남깁니다. 316 | fn(currVertex); // 방문 후 처리할 작업을 호출합니다. 317 | this.edges[index].forEach( val => { // 정점의 인접한 정점을 큐에 담습니다. 318 | queue.push(val.dest); 319 | }) 320 | } 321 | } 322 | } 323 | } 324 | ``` 325 | 326 | ## 최단 경로 찾기 (Shortest Path) 327 | 그래프를 이용해서 가장 많이 사용하는 동작은 바로 정점 간의 최단 경로를 찾는 것 입니다. 328 | 물론 최단 경로를 탐색하는 더 좋은 알고리즘도 있긴 합니다만 위에서 학습한 너비 우선 탐색을 가지고 최단 경로를 찾아보도록 하겠습니다. 329 | 330 | ## 너비 우선 검색을 통한 최단 경로 찾기 331 | 너비 우선 검색을 통한 방법은 2가지의 큰 틀에서 진행됩니다. 332 | 1. 시작 지점에서부터 갈 수 있는 모든 정점에 대한 간선 정보를 탐색합니다. 333 | 2. 위에 탐색한 간선 정보를 가지고 도작시점을 시작으로 도착지점으로 올 수 있는 임의의 간선을 하나 얻습니다. 334 | 3. 2번을 시작 지점이 나올 때까지 반복합니다. 335 | 336 | 위에서 수행하는 1번의 항목을 너비 우선 검색을 사용하여 해결할 것 입니다. 337 | 하지만 최단 경로를 찾기 위해서는 우리가 사용한 너비 우선 탐색을 조금 수정해야 합니다. 338 | 339 | 우리가 구현한 너비 우선 탐색에서 방문을 했다라고 체크했던 "visited[A]"는 "어딘가에서 이미 A에 왔었다"의 의미입니다. 340 | 하지만 우리가 필요한 정보는 위에서 얻은 정보의 어딘가에 대한 구체적인 언급이 필요합니다. 즉, "B에서 A로 왔다" 라는 정확한 어딘가가 필요합니다. 341 | 342 | 중복 코드가 발생하겠지만 가독성을 위해 우리는 "edgeTo"라는 새로운 메서드를 만들도록 하겠습니다. 343 | ```javascript 344 | class Graph { 345 | ... 346 | /** 347 | * 특정 정점을 기준으로 갈 수 있는 정점을 반환한다. 348 | * @param data : 연결된 간선 정보를 탐색할 시작 정점 349 | * @returns {Array} 350 | */ 351 | edgeTo(data) { 352 | const vertex = this.findVertex(data); // 정점의 객체를 얻어옵니다. 353 | const edgeTo = []; // 간정 정보를 기록할 배열입니다. 354 | const queue = []; // 탐색할 정점을 담을 대기 큐 355 | const visited = []; // 이미 방문한 정점을 기록할 배열입니다. 356 | queue.push(vertex); // 시작 정점을 큐에 담습니다. 357 | while(queue.length > 0) { // 더 이상 방문할 정점이 없을 때까지 반복합니다. 358 | const currVertex = queue.shift(); // 방문할 정점을 하나 꺼냅니다. 359 | const index = this.findVertexIndex(currVertex); // 정점의 인덱스를 얻어옵니다. 360 | this.edges[index].forEach( val => { // 정점에서 갈 수 있는 정점을 확인합니다. 361 | const innerIndex = this.findVertexIndex(val.dest); // 인접한 도착 가능한 정점의 인덱스를 얻어옵니다. 362 | if (!visited[innerIndex]) { // 아래는 기존 bfs와 다른 방문기록 방식입니다 363 | edgeTo[innerIndex] = currVertex; // 이전 정점(currVertex)에서 현재 정점으로 왔다는 정보를 edgeTo 배열에 등록합니다. 364 | visited[innerIndex] = true; // 위 정보를 기록 후 방문기록에 추가합니다. 365 | queue.push(val.dest); // 방문할 큐에 담습니다. 366 | } 367 | }) 368 | } 369 | return edgeTo; 370 | } 371 | } 372 | ``` 373 | bfs 메서드와의 차이점은 방문기록을 연결된 간선정보를 탐색하는 루프 안에서 체크한다는 점 입니다. 374 | 이는 반환할 edgeTo 배열을 작성한 후 방문기록을 등록한다는 점이 차이가 있습니다. 375 | 376 | 이제 우리는 출발지로부터 갈 수 있는 정점에 대한 정보를 수집하였습니다. 이 정보를 가지고 최단거리를 찾는 기능을 구현하도록 하겠습니다. 377 | ```javascript 378 | class Graph { 379 | ... 380 | /** 381 | * 너비 우선 탐색을 통해 얻은 간선 정보를 바탕으로 최단 거리를 반환합니다. 382 | * @param vertexSource : 출발 정점 383 | * @param vertexDestination : 도착 정점 384 | * @returns {*} 385 | */ 386 | pathFromTo(vertexSource, vertexDestination) { 387 | const edgeTo = this.edgeTo(vertexSource); // 시작 정점에서 갈 수 있는 정점을 반환합니다. 388 | const path = []; 389 | /* 390 | * 도착지점을 시작으로 시작 지점이 나올 때까지 edgeTo 배열을 추적합니다. 391 | */ 392 | for(let i = vertexDestination; i !== vertexSource; i = edgeTo[this.findVertexIndex(i)].data) { 393 | path.push(i); // 왔던 경로는 path 배열에 기록합니다. 394 | } 395 | path.push(vertexSource); // 마지막으로 시작 지점을 배열에 담습니다. 396 | return path.reverse(); // 해당 배열을 역으로 정렬합니다. 397 | } 398 | } 399 | ``` 400 | 401 | ## 위상 정렬 (Topological Sort) 402 | 위상 정렬은 방향성 그래프의 모든 방향성 간선이 한 방향으로만 향하도록 장점을 나열하는 것입니다. 403 | 위상정렬은 대그에 대하여 깊이 우선 탐색 알고리즘을 적용하면 간단히 구할 수 있습니다. 404 | 여기서 대그(Dag)란 무사이클 방향성 그래프를 뜻 합니다. 405 | 406 | 위상 정렬 또한 위에서 구현한 깊이 우선 탐색 메서드와 동작방식에 있어서 약간의 차이가 있습니다. 407 | 기존의 dfs는 방문한 정점을 즉시 작업(출력)을 하고 다음 정점을 방문했다면 위상 정렬은 인접한 정점을 모두 방문하고 작업을 진행하는 방식입니다. 408 | ```javascript 409 | class Graph { 410 | ... 411 | /** 412 | * 위상 정렬 팩토리 메서드 413 | * @param data : 위상 정렬을 시작할 정점 414 | * @returns {*} 415 | */ 416 | topSort(data) { 417 | const vertex = this.findVertex(data); // 정점의 객체를 얻어옵니다. 418 | const stack = []; // 정렬된 정점을 담을 스택용 배열입니다. 419 | this._topSort(vertex, val => { // 실제 위상 정렬을 수행하는 메서드를 호출하고, 콜백 함수를 정의합니다. 420 | stack.push(val); 421 | }); 422 | return stack.reverse(); // 배열을 스택형태로 한번에 꺼내기 위해 역순으로 반환합니다. 423 | } 424 | 425 | /** 426 | * 위상 정렬 427 | * @param vertex : 탐색할 정점 428 | * @param fn : 콜백 함수 429 | * @param visited : 방문한 정점의 배열 430 | */ 431 | _topSort(vertex, fn, visited = []) { 432 | const index = this.findVertexIndex(vertex); // 정점의 인덱스를 얻어옵니다. 433 | visited[index] = true; // 방문을 기록합니다. 434 | this.edges[index].forEach( v => { // 해당 정점에서 갈 수 있는 정점을 순회합니다. 435 | const innerIndex = this.findVertexIndex(v.dest); // 인접한 정점의 인덱스를 얻어옵니다. 436 | if (!visited[innerIndex]) { //방문하지 않은 정점이라면 깊이 우선 탐색을 진행합니다. 437 | this._topSort(v.dest, fn, visited); 438 | } 439 | }); 440 | fn(vertex.data); // 콜백 함수를 실행합니다. 441 | } 442 | } 443 | ``` 444 | 445 | 여기까지 그래프의 자료구조와 그래프의 순회에 대한 몇가지 기초적인 알고리즘을 살펴 보았습니다. 446 | 사실 매우 기초적인 알고리즘으로 데이터가 많아지면 성능적인 문제가 발생할 가능성이 큽니다. 447 | 또한 그래프 클래스에 너무 많은 기능을 넣다보니 구조적으로 비효율적인 형태도 일부 존재합니다. 448 | 자신이 필요로하는 최적화된 기능에 맞춰서 좀 더 가벼운 구조로 조정하여 사용하는 것이 바람직 합니다. --------------------------------------------------------------------------------