
여러개의 점들이 서로 복잡하게 연결되어 있는 관계를 표현한 자료구조
서로 다른 점들이 직접적인 관계를 가지고 있다면 바로 이어주는 선이 존재하고, 간접적인 관계를 가지고 있다면 다른 여러 점을 거쳐서 이어지는 선이 존재할 수 있다.
여기서 점은 그래프에서 정점(vertex)이라고 표현하고, 선은 간선(edge)라고 표현한다.
포털 사이트의 건색 엔진, SNS, 네이게이션 등에서 사용하는 자료구조가 바로 그래프이다.
let isConnected = {
seoul: {
busan: true,
daejeon: true
},
dajeon: {
seoul: true,
busan: true
},
busan: {
seoul: true,
daejeon: true
}
}
console.log(isConnected.seoul.daejeon) // true
console.log(isConnected.daejeon.busan) // true
비가중치 그래프는 각 정점 간의 연결 유뮤만을 판단하는 방면, 가중치 그래프는 더 자세한 정보를 담을 수 있다.


두 정점을 바로 이어주는 간선이 있다면 이 두 정점은 인접하다고 이야기한다.
인접 행렬은 정점들간의 인접함을 표시해 주는 행렬으로 2차원 배열의 모습을 가지고 있다.
만약 A라는 정점과 B라는 정점이 이어져 있다면 1, 이어져 있지 않다면 0으로 표시하는 일종의 표와 같은 모습이다.
만약 가중치 그래프라면 1 대신 관계에서 의미있는 값(위의 내비게이션 예제에선 거리)을 저장한다.
위 그래프를 인접 행렬로 표현하면

각 정점이 어떤 정점과 인접한지 리스트의 형태로 볼 수 있는 표현 방식이다.
각 정점마다 하나의 리스트를 가지고 있으며, 이 리스트에는 자신과 인접한 다른 정점들이 담겨 있다.

B는 A와 C로 이어지는 간선이 두 개가 있는데, 왜 A가 C보다 먼저일까? 순서가 중요할까?
class GraphWithAdjacencyMatrix {
constructor() {
this.matrix = [];
}
addVertex() { // 그래프에 버텍스를 추가
const currentLength = this.matrix.length;
for(let i = 0; i < currentLength; i++) {
this.matrix[i].push(0);
}
this.matrix.push(new Array(currentLength + 1).fill(0));
}
contains(vertex) { // 그래프에 해당 버텍스가 존재하는지 여부를 Boolean으로 반환
if(this.matrix[vertex]) {
return true;
} else {
return false;
}
}
addEdge(from, to) { // fromVertex 와 toVertex 사이의 간선을 추가
const currentLength = this.matrix.length;
if(from === undefined || to === undefined) {
console.log("2개의 인자가 있어야 합니다");
return;
}
// 간선을 추가할 수 없는 상황에서는 추가하지 말아야한다.
if(from + 1 > currentLength || to + 1 > currentLength || from < 0 || to < 0) {
console.log("범위가 매트릭스 밖에 있습니다");
return;
}
// 간선을 추가해야한다.
this.matrix[from][to] = 1
}
hasEdge(from, to) { // 두 버텍스 사이에 간선이 있는지 확인
if(this.matrix[from][to] === 1) {
return true;
} else {
return false;
}
}
removeEdge(from, to) {
const currentLength = this.matrix.length;
if(from === undefined || to === undefined) {
console.log("2개의 인자가 있어야 합니다.");
return;
}
// 간선을 지울 수 없는 상황에서는 지우지 말아야 한다.
if(from + 1 > currentLength || to + 1 > currentLength || from < 0 || to < 0) {
console.log("범위가 매트릭스 밖에 있습니다.")
return;
}
// 간선을 지워야 한다.
this.matrix[from][to] = 0;
}
}
그래프의 여러 구조 중 읿방향 그래프의 한 구조로, 그 모습이 나무와 닮아 있다고 해서 트리 구조라는 이름이 붙여 졌다.
하나의 뿌리로 부터 가지가 사방으로 뻗은 형태를 띄우고 있기 때문이다.

마치 가계도와 흡사해 보이는 이 트리 구조를 간략하게 정의하면, 데이터가 바로 아래에 있는 하나 이상의 데이터에 단방향으로 연결되는 계층적 자료구조라고 할 수 있다.
데이터를 순차적으로 나열시킨 형태인 선형구조가 아닌, 하나의 데이터 뒤에 여러개의 데이터가 존재할 수 있는 비선형 구조로 되어 있고, 계층적으로 표현이 되며 아래로만 벋기 때문에 사이클이 없다

트리 구조는 루트(Root)라는 하나의 꼭짓점 데이터를 시작으로 여러개의 데이터를 간선(edge)으로 연결한다.
이 데이터들을 노드(node)라고 하며, 상위 노드와 하위 노드가 연결이 되면 부모/자식 관계를 가지게 된다.
A는 B와 C의 부모 노드(Parent Node)가 되는 것이고,
B와 C는 A의 자식 노드(Child Node)가 되는 것이다.
자식이 없는 노드는 나무의 잎과 같다고 하여 리프 노드(Leaf Node)라고 부른다.

이 자료구조는 특이하게도 높이와 깊이를 잴 수 있다.
노드와 노드의 간격(거리)를 레벨(Level)이라고 부르며, 첫 번째 노드인 루트를 Level1로 설정한다.
루트부터 가장 안쪽에 있는 노드까지의 레벨을 트리의 높이(Height)라고 하고, 반대로 특정 노드부터 시작하여 루트까지의 레벨을 노드의 깊이(Depth)라고 표현한다.
같은 레벨에 나란히 있는 노드들은 형제 노드(Sibling Node)로 표현하고 있다.
root에서 뻗어나오는 큰 트리의 내부에 트리의 모양새를 갖춘 작은 트리를 서브 트리라고 부른다.(D-H-I, B-D-E, C-F-G-J)
가장 대표적으로, 컴퓨터의 디렉토리 구조를 생각할 수 있다.
모두 하나의 폴더에서 시작되어, 가지처럼 뻗어나가는 형태를 보이고 있다.
사용자들이 사용하기 편하게 하기 위한 파일 시스템과 같은 구현은 모두 트리를 이용해서 만든다.
class Tree {
constructor(value) { // constructor로 만든 객체는 트리의 Node가 된다.
this.value = value;
this.children = [];
}
insertNode(value) { // 트리의 삽입 메서드
// 값이 어떤 이름으로 만들어지고 어느 위치에 붙는지 떠올리는 것이 중요하다.
const childNode = new Tree(value);
this.children.push(childNode);
}
contains(value) { // 값이 포함되어 있다면 true를 반환
if(this.value === value) {
return true;
}
// 값을 찾을 때까지 children 배열을 순회하며 childNode를 탐색
for(let i = 0; i < this.children.length; i++) {
if(this.children[i].contains(value)) {
return true;
}
}
return false;
}
}
트리는 편리한 구조를 전시하는 것 외에 효율적인 탐색을 위해 사용하기도 한다.
가장 간단하고 많이 사용하는 트리의 모습은 이진 트리(binary tree)와 이진 탐색 트리(binary search tree)이다.
자식 노드가 최대 두 개인 노드들로 구성된 트리를 이진 트리라고 정의한다.
이 두 개의 노드는 왼쪽 자식과 오른쪽 자식으로 분류한다.
이진 트리는 자료의 삽입, 삭제 방법에 따라 정 이진 트리(Full Bianry Tree), 완전 이진 트리(Complete Binary Tree), 포화 이진 트리(Perfect Binary Tree)로 나뉜다.

모든 왼쪽 자식들은 루트나 부모보다 작은 값이고, 모든 오른쪽 자식들은 루트나 부모보다 큰 값인 특징을 가지고 있는 이진 트리를 이진 탐색 트리라고 정의한다.
이진 탐색 트리는 균형 잡힌 트리가 아닐 때, 입력되는 값으 순서에 따라 한 쪽으로 노드들이 몰리게 될 가능성이 있다.
이러한 문제를 해결하기 위해 삽입과 삭제마다 트리의 구조를 재조정하는 과정을 거치는 알고리즘을 도입할 수 있다.