ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ목표하던 바를 끝냈다.하하하하..구현하는 것보다 정리하는 것이 더 어려운 이유는 뭐지??
블로그 쓰는데만 반나절이 넘어가는 이유는 뭐지???
블로그 쓸때 집중력이 너무 구리다...구리구리...
그냥 개발만 하면 얼매나 조을까요....😩😩😩
오늘은 그동안 배웠던 자료구조가 어디에 쓰이는지도 정리해 보자!
크게 non-binary-tree, binary-tree로 나뉜다.
non-binary-tree
1) trie
binary-tree
1) 완전 이진 트리(complete binary tree) : 마지막 레벨을 제외하고는 모든 자식노드가 채워져있는 트리이다.
2) 포화 이진 트리(perfect binary tree) : 모든 노드의 차수가 0아니면 2를 가지며, 모든 리프노드가 같은 레벨에 있는 트리. 그냥 꽉 차 있는 트리라고 생각 ㄱㄱ
3) 정 이진 트리(full binary tree) : 모든 노드의 차수가 0아니면 2인 트리
4) 편향 이진 트리(skewed binary tree) : 말 그대로 노드들이 전부 한 방향으로 편향된 트리이다.
5) 이진 탐색 트리(binary search tree) : 왼쪽 자식노드의 값이 부모노드보다 작고, 오른쪽 자식노드의 값이 루트노드보다 큰 트리를 이진 탐색 트리라고 한다.
이진 탐색 트리는 루트노드보다 계속 큰 값이 들어 올 경우에, 불균형한 편향 이진 트리가 될 수 있다.
그래서 "Balanced binary search tree"가 등장
“Splay tree”, “AVL tree”, “Red Black tree” 가 대표적인 예시이다.
균형이 잡힌 tree의 탐색, 추가, 삭제는 O(logN)의 시간 복잡도를 가진다.
균형이 잡히지 않고 한쪽으로 치우칠수록 tree의 탐색, 추가, 삭제는 O(N)의 시간 복잡도를 가질 확률이 올라간다.
Tree의 탐색(중요)
- 다음과 같은 방법으로 트리의 노드를 순회한다.
1) 전위순회(pre-order) : "부모 - 좌 - 우"
2) 중위순회(in-order) : "좌 - 부모 - 우"
3) 후위순회(post-order) : "좌 - 우 - 부모"
Time Complexity : logN
"흔히 알고리즘을 공부하다보면 logN의 시간 복잡도를 심심치 않게 만나게 된다.
이미 대다수의 사람들이 트리를 사용할 때 시간 복잡도가 로그 값이 나온다는 사실에 대해서 알고 있을 것이다.
이때, 많은 사람들이 이 로그의 값이 어디에서 나오게 된 것인지 제대로 이해를 하지 않고, 단순히 암기를 하려고 한다.
사실, logN의 시간 복잡도가 나오는 이유는 그리 어렵지 않다.
예를 들어서 2개의 자식 노드를 가지는 이진 트리(binary tree)를 이용해서 M개의 값들 중에서 원하는 값을 찾는다고 해 보자.
처음에는 M개의 값들 모두 탐색 대상이지만, 루트 노드에서 첫 번째 자식 노드 층으로 이동하게 되면 그 수가 절반으로 줄게 된다.
이제 M/2개의 값들이 남게 되는데, 다시 한번 다음 자식 노드 층으로 넘어가게 되면 M/4개의 값들이 남게 된다.
이런 식으로 매번 층을 내려갈 때마다 남은 값의 수들이 절반이 되게 된다.
만약 탐색을 해야 하는 자료의 수가 2^n 개라면, 이진 트리를 사용할 경우 n번의 탐색을 통해서 원하는 값을 찾을 수 있게 된다.
log_2(2^n) = n 이기 때문에, 이진 탐색 트리(Binary Search Tree)를 이용한 이진 탐색 기법의 시간 복잡도가 log_2(N)이 되는 것이다.
다시 말해서, 자식 노드의 수가 m개인 트리로 N개의 자료에서 원하는 값을 탐색하는 알고리즘의 시간 복잡도는 log_m(N)이 된다."
[출처: tstory블로그, https://neos518.tistory.com/145 [As I've always been]]
class BinarySearchTreeNode {
constructor(value) {
this.value = value;
this.left = null;
this.right = null;
}
insert(value) {
let newNode = new BinarySearchTreeNode(value);
function recursion(node) {
if (node.value < newNode.value) {
if (node.right) {
recursion(node.right);
}
else {
node.right = newNode;
}
}
else if (node.value > newNode.value) {
if (node.left) {
recursion(node.left);
}
else {
node.left = newNode;
}
}
}
recursion(this);
}
contains(value) {
let hasVal = false;
function recursion(arr) {
let hasCNodeArr = [];
for (let item of arr) {
if (item.value === value) {
hasVal = true;
return;
}
if (item.left) {
hasCNodeArr = hasCNodeArr.concat(item.left);
}
if (item.right) {
hasCNodeArr = hasCNodeArr.concat(item.right);
}
}
if (hasCNodeArr.length > 0) {
recursion(hasCNodeArr)
}
}
recursion([this.left, this.right])
return hasVal;
}
inorder(callback) {
function travel(node){
if(node !== null){
travel(node.left);
callback(node.value);
travel(node.right);
}
}
travel(this);
}
}
1) 무방향 그래프(Undirected Graph)
- 엣지에 방향이 없는 그래프이다.
- 정점 A와 B를 연결하는 간선을 [A, B]라고 하면, 이때 [A, B]와 [B, A]는 같은 엣지이다.
2) 방향 그래프(Directed Graph)
- 방향 그래프는 다이그래프(digraph)라고도 하는데, 엣지에 방향이 있는 그래프이다.
- 정점 A와 정점 B를 연결하는 엣지를 [A, B]라고 하면, 이때 [A, B]와 [B, A]은 같을 수 없다, [A, B]는 정점 A에서 B로만 갈 수 있는 엣지를 의미한다.
3) 완전 그래프(Complete Graph)
- 완전 그래프는 한 정점에서 모든 다른 정점과 연결되어 최대의 간선수를 가지는 그래프다.
- 무방향 그래프에서의 최대 간선 수 : n(n-1)/2
- 방향 그래프에서의 최대 간선 수 : n(n-1)/1
4) 부분 그래프(subgraph)
- 기존의 그래프에서 일부의 정점이나 엣지를 제외하여 만든 그래프를 의미한다.
5) 가중 그래프(Weight Graph)
- 엣지에 가중치를 할당한 그래프를 의미한다.
그래프는 구현 방법에 따라 시간 복잡도가 다르다.
구현 방법은 인접 배열, 인접 리스트 두 가지가 있다.
V = 정점(Vertex)의 개수
E = 간선(Edge)의 개수
1) 인접 리스트
2) 인접 배열
하나의 정점에서 시작하여 그래프에 있는 모든 정점을 한번씩 방문하는 것을 그래프 순회 또는 그래프 탐색 이라고 한다. 그래프 탐색 방법은 깊이 우성 탐색과 너비 우선 탐색이 있다.
1) 깊이 우선 탐색, DFS(Depth First Search) : 스택, 재귀함수 사용
깊이 우선 탐색은 시작 정점에서 한 방향으로 갈 수 있는 가장 먼 경로까지 깊이 탐색해가다가 더 이상 갈 곳이 없으면 가장 마지막에 만났던 갈림길 간선이 있는 정점으로 되돌아와서 다른 방향의 간선으로 탐색을 계속함으로써 모든 정점을 방문하는 순회 방법이다.
2) 너비 우선 탐색, BFS(Breath Frist Search) : 큐 사용
너비 우선 탐색은 시작 정점으로 부터 인접한 정점들을 모두 차례로 방문한 후, 발문했던 정점을 다시 시작점으로 하여 인접한 정점들을 차례로 방문하는 방법으로, 가까운 정점들을 먼저 방문하고 멀리 있는 정점들은 나중에 방문하는 순회 방법이다.
class Graph {
constructor() {
/*
* ex)
* nodes = {
* 0: [ 1, 2 ],
* 1: [ 0 ],
* 2: [ 0 ]
* }
*/
this.nodes = {};
}
addNode(node) {
this.nodes[node] = this.nodes[node] || [];
}
contains(node) {
return this.nodes[node] ? true : false;
}
removeNode(node) {
this.nodes[node].forEach(function(item){
this.removeEdge(node, item);
})
delete this.nodes[node]
}
hasEdge(fromNode, toNode) {
if(this.nodes[fromNode] && this.nodes[toNode]){
return this.nodes[fromNode].includes(toNode) && this.nodes[toNode].includes(fromNode)
}
else{
return false;
}
}
addEdge(fromNode, toNode) {
this.nodes[fromNode].push(toNode);
this.nodes[toNode].push(fromNode);
}
removeEdge(fromNode, toNode) {
this.nodes[fromNode].splice(this.nodes[fromNode].indexOf(toNode), 1);
this.nodes[toNode].splice(this.nodes[toNode].indexOf(fromNode), 1);
}
}
Stack : 프로그래밍 언어의 런타임에서 함수의 호출을 관리(Call Stack), undo(이게뭔지 알아보자), 재생목록관리
Queue : 메세지 큐, 메세지 브로커, node.js 런타임에서 이벤트 큐, BFS(너비우선탐색)
Linked List : 파일시스템(자료의 추가 / 삭제가 빈번히 일어날 경우에 사용)
Hash Table : 딕셔너리(객체), Map, p2p, 시스템의 노드 주소관리, 로드밸런싱
Tree : binary search tree, 2-3-4 tree, b-tree, radix tree(자동완성, 검색), patricia tree
Graph : 네비게이션, sns
요새 스타트업이라는 드라마가 방영중인데, IT창업을 주제로 하는 드라마라서 흥미가 간다.
주인공이 영상처리로 카메라에 담기는 객체를 분석하는데, 옛날에 학부시절에 한 것들이 생각이 났다.
그거 깃허브에 담는건 괜찮은데.....블로그에 내용을 포스팅 하려니 골이 아프다....ㅎ
하... 갑자기 backpropagation이 생각남...
아~ 척추뽑히겠당~
그만 써야겠다ㅎㅎ
👋👋
전 구현도 어렵고, 블로깅도 어려운데... 어쩌죠. ㅠㅠㅠㅠㅠ