├── .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 | 
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 | 
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 | vertices와 edges의 인덱스는 동일한 정점의 인덱스를 가지고 있다고 보면 됩니다.
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 | 자신이 필요로하는 최적화된 기능에 맞춰서 좀 더 가벼운 구조로 조정하여 사용하는 것이 바람직 합니다.
--------------------------------------------------------------------------------