트리(Tree)란 무엇인가?

트리(Tree)


트리(Tree)란, 부모/자식관계를 가지는 구조로, 계층적 관계(Hierarchical Relationship)를 표현하는 자료구조이다.


트리(Tree)의 용어


Tree
출처: Tree

  • 루트 노드(root node): 트리의 최상단 노드로, 트리는 하나의 루트 노드만을 가짐.
  • 단말 노드(leaf node): 자식이 없는 노드로, ‘말단 노드’ 또는 ‘잎 노드’라고도 함.
  • 내부(internal) 노드: 단말 노드가 아닌 노드가 아닌 노드
  • 간선(edge): 노드를 연결하는 선 (link, branch 라고도 부름)
  • 형제(sibling): 같은 부모를 가지는 노드
  • 노드의 크기(size): 자신을 포함한 모든 자손 노드의 개수
  • 노드의 깊이(depth): 루트에서 어떤 노드에 도달하기 위해 거쳐야 하는 간선의 수
  • 노드의 레벨(level): 트리의 특정 깊이를 가지는 노드의 집합
  • 노드의 차수(degree): 하위 트리 개수 / 간선 수 (degree) = 각 노드가 지닌 가지의 수
  • 트리의 차수(degree of tree): 트리의 최대 차수
  • 트리의 높이(height): 루트 노드에서 가장 깊숙히 있는 노드의 깊이


트리(Tree)의 특징


  • 트리는 그래프의 한 형태이다. 즉, 그래프 안에 여러 종류 중 트리가 있다.
  • 트리는 방향이 위에서 아래로만 향하는 방향 그래프이다. 즉, 노드들 사이의 선을 간선(edge)라고 하는데, 트리는 간선의 방향이 위에서 아래이다.
  • 트리는 DAG(Directed Acyclic Graphs, 방향성이 있는 비순환 그래프)의 한 종류이다. 즉, loop나 circuit이 없고, self-loop도 없다. 즉, 사이클이 없다.
  • 한 개의 루트 노드만이 존재하며 모든 자식 노드는 한 개의 부모 노드만을 가진다.


트리(Tree)의 종류


  • 서브 트리(Sub Tree): 전체 트리는 작은 트리로 구성되어 있는데, 이 작은 트리를 서브 트리(Sub Tree)라고 한다.
  • 이진 트리(Binary Tree): 자식 노드가 최대 2개까지만 붙는 트리를 이진 트리(Binary Tree)라고 한다. 즉, 자식 노드가 1개이거나, 2개일 경우 모두 이진 트리이다.

Binary Tree
출처: Binary Tree

  • 이진 검색 트리(Binary Search Tree): 노드안에 데이터가 왼쪽 노드와 그 이하 자식 노드들은 현재 노드보다 작아야 하고, 오른쪽 노드와 그 이하 노드들은 현재 노드보다 커야 한다. 이 조건을 만족하는 트리를 이진 검색 트리(Binary Search Tree)라고 한다. 노드를 보고, 해당 노드보다 큰 값을 찾고 싶으면, 오른쪽에서 찾으면 되고, 해당 노드보다 작은 값을 찾고 싶으면, 왼쪽에서 찾으면 된다. 따라서, 어떤 값을 찾을 때, 두 갈래 길을 선택해서 가다보면, 그 값을 찾을 수 있다.

Binary Search Tree
출처: Binary Search Tree

13. Printer(Stack/Queue)

[Problem]

일반적인 프린터는 인쇄 요청이 들어온 순서대로 인쇄합니다. 그렇기 때문에 중요한 문서가 나중에 인쇄될 수 있습니다. 이런 문제를 보완하기 위해 중요도가 높은 문서를 먼저 인쇄하는 프린터를 개발했습니다. 이 새롭게 개발한 프린터는 아래와 같은 방식으로 인쇄 작업을 수행합니다.

  1. 인쇄 대기목록의 가장 앞에 있는 문서(J)를 대기목록에서 꺼냅니다.
  2. 나머지 인쇄 대기목록에서 J보다 중요도가 높은 문서가 한 개라도 존재하면 J를 대기목록의 가장 마지막에 넣습니다.
  3. 그렇지 않으면 J를 인쇄합니다.

예를 들어, 4개의 문서(A, B, C, D)가 순서대로 인쇄 대기목록에 있고 중요도가 2 1 3 2 라면 C D A B 순으로 인쇄하게 됩니다. 내가 인쇄를 요청한 문서가 몇 번째로 인쇄되는지 알고 싶습니다. 위의 예에서 C는 1번째로, A는 3번째로 인쇄됩니다.

현재 대기목록에 있는 문서의 중요도가 순서대로 담긴 배열 priorities와 내가 인쇄를 요청한 문서가 현재 대기목록의 어떤 위치에 있는지를 알려주는 location이 매개변수로 주어질 때, 내가 인쇄를 요청한 문서가 몇 번째로 인쇄되는지 return 하도록 solution 함수를 작성해주세요.


[Algorithms]

  • queue의 push/shift를 이용한 문제
  • Array 중에서 최대값을 구하고, 각 첫 요소가 최대값보다 같거나 크면, queue로 push한다.
  • 이때, 그 첫 요소가 우리가 찾는 값이면, 그 값의 순서는 queue.length(사이즈)로 몇번째인지 알 수 있다.
  • array는 요소의 값이 아니라, 우선순위만 나와있기 때문에, 순서가 같을 수 있다. 그래서 그 첫 요소가 우리가 찾는 값인지 알기 위해, 두번째 인자로 제공되는 location의 인덱스를 활용한다. location을 계속 array안에서 변경시키면서 확인한다.
  • 그리고, 첫번째 요소만 확인하기 위해, while(i === 0)일때를 사용하여, 계속 첫번째 값만 확인하게 한다.


[Solution]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function solution(priorities, location) {
let answer = 0;
let queue = [];
let i = 0;

while(i === 0) {
let max = Math.max(...priorities); // max = 9
if(priorities[i] < max) {
const notMaxValue = priorities.shift();
priorities.push(notMaxValue); // [1, 3, 2, 2]
location === 0 ? location = priorities.length - 1 : location = location - 1;
} else if(priorities[i] >= max) {
const maxValue = priorities.shift(); // 9
queue.push(maxValue); // queue = [9], priorities = [1, 1, 1, 1, 1]
if(i === location) {
return answer = queue.length;
} else {
location = location - 1;
}
}
}

return answer;
}
console.log(solution([1, 1, 9, 1, 1, 1], 0)); // 5

12. 짝이 맞지 않는 괄호 문제(Stack/Queue)

[Problem]

여러 개의 쇠막대기를 레이저로 절단하려고 합니다. 효율적인 작업을 위해서 쇠막대기를 아래에서 위로 겹쳐 놓고, 레이저를 위에서 수직으로 발사하여 쇠막대기들을 자릅니다. 쇠막대기와 레이저의 배치는 다음 조건을 만족합니다.

  • 쇠막대기는 자신보다 긴 쇠막대기 위에만 놓일 수 있습니다.
  • 쇠막대기를 다른 쇠막대기 위에 놓는 경우 완전히 포함되도록 놓되, 끝점은 겹치지 않도록 놓습니다.
  • 각 쇠막대기를 자르는 레이저는 적어도 하나 존재합니다.
  • 레이저는 어떤 쇠막대기의 양 끝점과도 겹치지 않습니다.

아래 그림은 위 조건을 만족하는 예를 보여줍니다. 수평으로 그려진 굵은 실선은 쇠막대기이고, 점은 레이저의 위치, 수직으로 그려진 점선 화살표는 레이저의 발사 방향입니다.

쇠막대기와 레이저

이러한 레이저와 쇠막대기의 배치는 다음과 같이 괄호를 이용하여 왼쪽부터 순서대로 표현할 수 있습니다.

(a) 레이저는 여는 괄호와 닫는 괄호의 인접한 쌍 ‘()’으로 표현합니다. 또한 모든 ‘()’는 반드시 레이저를 표현합니다.
(b) 쇠막대기의 왼쪽 끝은 여는 괄호 ‘(‘로, 오른쪽 끝은 닫힌 괄호 ‘)’로 표현됩니다.

위 예의 괄호 표현은 그림 위에 주어져 있습니다.
쇠막대기는 레이저에 의해 몇 개의 조각으로 잘리는데, 위 예에서 가장 위에 있는 두 개의 쇠막대기는 각각 3개와 2개의 조각으로 잘리고, 이와 같은 방식으로 주어진 쇠막대기들은 총 17개의 조각으로 잘립니다.

쇠막대기와 레이저의 배치를 표현한 문자열 arrangement가 매개변수로 주어질 때, 잘린 쇠막대기 조각의 총 개수를 return 하도록 solution 함수를 작성해주세요.


[Algorithms]

  • ‘(’면 stack에 push해서 저장한다.
  • ‘)’일때, 이게 레이저냐 파이프냐에 따라 나뉜다.
  • 레이저일 경우, stack.size() 또는 stack.length를 이용해 쇠막대기 수를 계산한다.
  • 이때, 레이저인지는 앞에 입력 문자열의 전 문자가 ‘(‘일때이다.
  • 파이프일 경우, 쇠막대기 수에 1를 더한다.
  • 이때, 파이프인지는 앞에 입력 문자열의 전 문자가 ‘)’일때이다.
  • 중요한건,
    • 기본적으로 이런 문제는 stack(배열)에 push/pop을 이용한다.
    • stack의 사이즈(크기)를 이용하여 수를 구한다.


[Solution]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const solution = arrangement => {
let answer = 0; // 전체 조각난 쇠막대기 수
let stack = [];
let ironBar = 0; // 조각난 쇠막대기 수 ------

for(let i = 0; i < arrangement.length; i++) {
if(arrangement[i] === '(') {
stack.push(arrangement[i]);
} else if(arrangement[i] === ')') {
// check if it is lazer or pipe
stack.pop();
// if lazer
if (arrangement[i-1] === '(') {
ironBar = stack.length;
answer += ironBar;
// if pipe
} else if (arrangement[i-1] === ')') {
answer += 1;
}
}
}
return answer;
}

console.log(solution('()(((()())(())()))(())')) // 17

11. 가장 큰 수(정렬)

[Problem]

0 또는 양의 정수가 주어졌을 때, 정수를 이어 붙여 만들 수 있는 가장 큰 수를 알아내 주세요. 예를 들어, 주어진 정수가 [6, 10, 2]라면 [6102, 6210, 1062, 1026, 2610, 2106]를 만들 수 있고, 이중 가장 큰 수는 6210입니다.

0 또는 양의 정수가 담긴 배열 numbers가 매개변수로 주어질 때, 순서를 재배치하여 만들 수 있는 가장 큰 수를 문자열로 바꾸어 return 하도록 solution 함수를 작성하시오.


[Algorithms]


[Solution]

1
2
3
4
5
6
7
8
9
10
11
const solution = numbers => {

// 배열의 각 요소를 문자열로 만들기
const answer = numbers.map(v=> v+'') // [ '3', '30', '34', '5', '9' ]
// 문자열이지만, 값이 숫자인 요소들을 앞 숫자가 큰 요소로 정렬하기
.sort((a,b) => Number(b+a) - Number(a+b)) // [ '9', '5', '34', '3', '30' ]
.join('');

return answer[0] === '0' ? '0' : answer;
}
console.log(solution([3, 30, 34, 5, 9])) // '9534330'

10. K번째 수(정렬)

[Problem]

배열 array의 i번째 숫자부터 j번째 숫자까지 자르고 정렬했을 때, k번째에 있는 수를 구하려 합니다. 예를 들어 array가 [1, 5, 2, 6, 3, 7, 4], i = 2, j = 5, k = 3이라면,

  1. array의 2번째부터 5번째까지 자르면 [5, 2, 6, 3]입니다.
  2. 1에서 나온 배열을 정렬하면 [2, 3, 5, 6]입니다.
  3. 2에서 나온 배열의 3번째 숫자는 5입니다.

배열 array, [i, j, k]를 원소로 가진 2차원 배열 commands가 매개변수로 주어질 때, commands의 모든 원소에 대해 앞서 설명한 연산을 적용했을 때 나온 결과를 배열에 담아 return 하도록 solution 함수를 작성하시오.


[Algorithms]


[Solution]

1
2
3
4
5
6
7
8
9
10
11
12
function solution(array, commands) {
let answer = [];
for(let i = 0; i < commands.length; i++) {
const sliceArray = array.slice(commands[i][0] - 1, commands[i][1]); // [ 5, 2, 6, 3 ]
const sortedArray = sliceArray.sort((a, b) => { return a - b }); // [ 2, 3, 5, 6 ]
const value = sortedArray[commands[i][2] - 1]; // 5
answer.push(value);
}
return answer;
}

console.log(solution([1, 5, 2, 6, 3, 7, 4], [[2, 5, 3], [4, 4, 1], [1, 7, 3]])); // [ 5, 6, 3 ]

9. 최대공약수와 최소공배수

[Problem]

두 수를 입력받아 두 수의 최대공약수와 최소공배수를 반환해주는 gcdlcm() 함수를 작성하시오.

  • 예를 들어, 3과 12의 두 숫자의 최대공약수와 최소공배수를 비교한다고 해보자.
  • 3의 약수는 1, 3이고, 12의 약수는 1, 2, 3, 4, 6, 12이다. 여기서, 최대공약수는 3이다.
  • 3의 배수는 3, 6, 9, 12, 15, 18… 이다. 12의 배수는 12, 24, 36, 48… 이다. 여기서, 최소공배수는 12이다.


[Algorithms]


[Solution]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function gcdlcm(a, b) {
let gcd = 1, lcm = 1;
const minNum = Math.min(a,b);

// 두 숫자 중에서 작은 값을 찾아, 1부터 그 작은값까지 비교하면서, 두 숫자에 대한 공약수를 찾는다.
for (let i = 1; i <= minNum; i++) {
if ((a%i == 0) && (b%i == 0)) { gcd = i }
};

// 최소공배수는 두 수의 곱에 최대공약수로 나눈 값이다.
lcm = a*b/gcd;
return [gcd, lcm]
}

console.log(gcdlcm(3,12)); // [ 3, 12 ]

8. 두 정수 사이의 함

[Problem]

두 정수 a, b가 주어졌을 때, a와 b 사이에 속한 모든 정수의 합을 리턴하는 함수, sumInt() 함수를 작성하시오.


[Algorithms]


[Solution]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function sumInt(a, b) {
let answer = 0;

const sortNumber = (a, b) => {
return Array.prototype.slice.call(arguments).sort(function(a,b){
return a - b;
})
}

const newArray = sortNumber(a, b); // [ 1, 5 ]
if (newArray[0] === newArray[1]) {
answer = newArray[0];
} else {
for (let i = newArray[0]; i <= newArray[1]; i++){
answer += i;
}
}

return answer;
}

console.log(sumInt(1, 5)); // 15 -> sum of 1 ~ 5

7. 나누어 떨어지는 숫자 배열

[Problem]

array의 각 element 중 divisor로 나누어 떨어지는 값을 오름차순으로 정렬한 배열을 반환하는 함수, solution을 작성하시오.


[Algorithms]


[Solution]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const solution = (arr, divisor) => {
let answer = [];
for (let i = 0; i < arr.length; i++) {
if(arr[i]%divisor === 0) {
answer.push(arr[i]);
}
}
if (typeof answer !== 'undefined' && answer.length > 0) {
answer.sort((a, b) => {
return a - b;
});
} else {
answer.push(-1);
}
return answer;
}

console.log(solution([6, 7, 10, 3, 12], 3)); // [ 3, 6, 12 ]
console.log(solution([], 3)); // [ -1 ]

6. 가운데 글자 가져오기

[Problem]

단어 str의 가운데 글자를 반환하는 함수, solution을 작성하시오. 만약, 단어의 길이가 짝수라면 가운데 두글자를 반환하면 됩니다.


[Algorithms]


[Solution]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const solution = str => {
let answer = '';
const stringLen = str.length;
const stringHalf = Math.floor(stringLen/2);
let stringStart = '';
const stringEnd = stringHalf + 1;

if (stringLen % 2 === 0) {
stringStart = stringHalf - 1;
answer = str.substring(stringStart, stringEnd);
} else if (stringLen % 2 === 1) {
stringStart = stringHalf;
answer = str.substring(stringStart, stringEnd);
}

return answer;
}

console.log(solution('names')); // m
console.log(solution('name')); // am

5. Harmless Ransom Note

What is Harmless Ransom Note?

FacHarmless Ransom Note란, 중복되는 문자열의 유무를 파악해서 true/false로 확인하는 것을 말한다.


[Problem]

2개의 문자열 중, 중복되는 문자열의 유무를 파악해서 true/false로 반환하는 함수를 작성하시오. 즉, 왼쪽 문자열의 각 단어들이 오른쪽 문자열에 1개라도 포함이 안되거나, 오른쪽 문자열의 각 동일한 단어보다 더 많을 경우, false를 반환한다.

  • 예를 들어, 왼쪽 문자열이 ‘this is my my note’ 이고, 오른쪽 문자열이 ‘this is is my note’일 때, 왼쪽 문자열의 각 단어 중 my는 왼쪽 문자열에서 2번있는데, 오른쪽에서는 1번 밖에 없으면 false를 반환해야 한다.
  • 또한, 왼쪽 문자열이 ‘this is my special note’ 이고, 오른쪽 문자열이 ‘this is is my note’일 때, 왼쪽 문자열의 special 단어는 오른쪽 문자열에 없어도, false를 반환해야 한다.


[Algorithms]


[Solution]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
const harmlessRansomeNote = (noteText, magazineText) => {
//split both string parameters into arrays
let noteArr = noteText.split(' ');
let magArr = magazineText.split(' ');

//create an object to use as a hash table it also avoids exponential time complexity of nested loops
let magObj = {};


//forEach to check if word is an word is an object on our property
magArr.forEach(word => {
//if not we add it to our object
//increment the value
if (!magObj[word]) {
magObj[word] = 1;
} else {
//otherwise, if it is there
//we just increment the value
magObj[word]++;
}
}); // { this: 1, is: 2, my: 1, note: 1 }

//Boolean variable to be return from function
let noteIsPossible = true;

//use forEach to iterate through each word
noteArr.forEach(word => {
//if word is found on the object decrement it
if (magObj[word]) {
magObj[word]--;
//if word value < 0 we can't make our word so note is NOT possible.
if (magObj[word] < 0) {
noteIsPossible = false;
}
//if word is not found then the note is NOT possible.
} else {
noteIsPossible = false;
}
});

return noteIsPossible;
};

console.log(harmlessRansomeNote('this is my note', 'this is is my note')); // true