이진트리
이진트리란 자식 노드가 최대 두 개인 노드들로 구성된 트리이다.
컴퓨터 과학에서 이진 탐색 트리(BST: binary search tree)는 다음과 같은 속성이 있는 이진 트리 자료 구조이다.
- 각 노드에 값이 있다.
- 값들은 전순서가 있다.
- 노드의 왼쪽 서브트리에는 그 노드의 값보다 작은 값들을 지닌 노드들로 이루어져 있다.
- 노드의 오른쪽 서브트리에는 그 노드의 값과 같거나 큰 값들을 지닌 노드들로 이루어져 있다.
- 좌우 하위 트리는 각각이 다시 이진 탐색 트리여야 한다.

포화 이진트리
모든 노드가 두 개의 자식 노드를 가지며 모든 잎 노드가 동일한 레벨을 갖는다.

완전 이진트리
마지막 레벨을 제외하고 모든 레벨이 완전히 채워져 있으며, 마지막 레벨에서는 왼쪽부터 노드가
순서대로 채워져 있다.

이진트리의 순회
전위 순회
루트 노드부터 잎 노드까지 아래 방향으로 순회
- 자신 노드
- 왼쪽 노드
- 오른쪽 노드

중위 순회
왼쪽 하위 노드부터 오른쪽 하뤼 노드 방향으로 방문
- 왼쪽 노드
- 자신 노드
- 오른쪽 노드

후진 순회
잎 노드를 모두 탐색하고 루트 노드를 방문
- 왼쪽 노드
- 오른쪽 노드
- 자신 노드

이진 탐색 트리
탐색을 위한 이진트리
왼쪽 자식 노드는 자신보다 작고, 오른쪽 자식 노드는 자신보다 크다는 특징이 있다.
깊이 우선 탐색
깊이 우선 탐색 (depth-first search: DFS)은 맹목적 탐색방법의 하나로 탐색트리의 최근에 첨가된 노드를 선택하고, 이 노드에 적용 가능한 동작자 중 하나를 적용하여 트리에 다음 수준(level)의 한 개의 자식노드를 첨가하며, 첨가된 자식 노드가 목표노드일 때까지 앞의 자식 노드의 첨가 과정을 반복해 가는 방식이다. (출처 :https://ko.wikipedia.org/wiki/깊이_우선_탐색)
깊이 우선 탐색은 스택(stack)을 사용해서 탐색한다.

노란색 노드는 아직 방문하지 않은 노드, 초록색 노드는 방문한 노드이며, 스택에 담겨서 Current로, Current에서 방문경로로 이동하는 순간 그 노드와 연결되어 있던 노드들이 스택(Stack)으로 들어가게 된다.(문제 5번 설명 참고)

너비 우선 탐색
너비 우선 탐색(Breadth-first search, BFS)은 맹목적 탐색방법의 하나로 시작 정점을 방문한 후 시작 정점에 인접한 모든 정점들을 우선 방문하는 방법이다. 더 이상 방문하지 않은 정점이 없을 때까지 방문하지 않은 모든 정점들에 대해서도 너비 우선 검색을 적용한다. OPEN List 는 큐를 사용해야만 레벨 순서대로 접근이 가능하다. (출처 :https://ko.wikipedia.org/wiki/너비_우선_탐색)

노란색 노드는 아직 방문하지 않은 노드, 초록색 노드는 방문한 노드이며, 스택에 담겨서 Current로, Current에서 방문경로로 이동하는 순간 그 노드와 연결되어 있던 노드들이 큐(Queue)로 들어가게 된다.(문제 5번 설명 참고)

이진 탐색 트리에서의 검색
- 이진탐색트리에서 키 x를 가진 노드를 검색하고자 할때, 트리에 해당 노드가 존재하면 해당 노드를 리턴하고, 존재하지 않으면 NULL을 리턴한다.
- 검색하고자 하는 값을 루트노드와 먼저 비교하고, 일치할 경우 루트노드를 리턴한다.
- 불일치하고 검색하고자 하는 값이 루트노드의 값보다 작을 경우 왼쪽 서브트리에서 재귀적으로 검색한다.
- 불일치하고 검색하고자 하는 값이 루트노드의 값과 같거나 큰 경우 오른쪽 서브트리에서 재귀적으로 검색한다.
삽입

- 삽입을 하기 전, 검색을 수행한다.
- 트리를 검색한 후 키와 일치하는 노드가 없으면 마지막 노드에서 키와 노드의 크기를 비교하여서 왼쪽이나 오른쪽에 새로운 노드를 삽입한다.
- Java 코드
// root는 전역변수 public void insertNode(int key) { Node newNode = new Node(key); if (root == null) { root = newNode; return; } Node current = root; Node prev = null; while (current != null) { prev = current; if (key == current.data) { return; } else if (key < current.data) { current = current.left; } else { current = current.right; } } if (key < prev.data) { prev.left = newNode; } else { prev.right = newNode; } }
- Python 코드
def 삽입(값, 노드) : if 값 < 노드.값 : if 노드.왼쪽자식 is None : 노드.왼쪽자식 = TreeNode(값) else : 삽입(값, 노드.왼쪽자식) else 값 > 노드.값 : if 노드.오른쪽자식 is None : 노드.오른쪽자식 = TreeNode(값) else : 삽입(값, 노드.오른쪽자식)
- Javascript 코드
function 삽입(data){ //노드 생성 const node = new Node(data, null, null); //루트노드가 존재하지 않을 경우 if(this.root == null){ this.root = node; } else { let current = this.root; let parent; while(true){ parent = current; if(data < current.data){ current = current.left; if(current == null){ parent.left = node; break; } } else { current = current.right; if(current == null){ parent.right = node; break; } } } } }
삭제
삭제하려는 노드의 자식 수에 따라
- 자식노드가 없는 노드(리프 노드) 삭제 : 해당 노드를 단순히 삭제한다.
- 자식노드가 1개인 노드 삭제 : 해당 노드를 삭제하고 그 위치에 해당 노드의 자식노드를 대입한다.
- 자식노드가 2개인 노드 삭제 : 삭제하고자 하는 노드의 값을 해당 노드의 왼쪽 서브트리에서 가장 큰 값으로 대체하거나, 오른쪽 서브트리에서 가장 작은 값으로 대체한 뒤, 해당 노드(왼쪽 서브트리에서 가장 큰 값을 가지는 노드 또는 오른쪽 서브트리에서 가장 작은 값을 가지는 노드)를 삭제한다.
- Java 코드
public void deleteNode (int key) { Node prev = null; Node current = root; while (current != null && current.data != key) { prev = current; if (key < current.data) { current = current.left; } if (key > current.data) { current = current.right; } } if (current == null) { System.out.println("data is not in the tree"); return; } // 단말노드인 경우 if (current.left == null && current.right == null) { if (prev != null) { if (prev.left == current) { prev.left = null; } else { prev.right = null; } } else { root = null; } } // 하나의 자식만 가지는 경우 else if (current.left == null || current.right == null) { Node child = (current.left != null) ? current.left : current.right; if (prev != null) { if (prev.left == current) { prev.left = child; } else { prev.right = child; } } else { root = child; } } // 두 개의 자식을 가지는 경우 else { // 오른쪽 서브트리에서 successor를 찾는다. Node succ_p = current; Node succ = current.right; while (succ.left != null) { succ_p = succ; succ = succ.left; } if (succ_p.left == succ) { succ_p.left = succ.right; } else { succ_p.right = succ.right; } current.data = succ.data; current = succ; } }
- Python 코드
def 삭제(삭제값, 노드) : if 노드 is None : return None elif 삭제값 < 노드.값 : 노드.왼쪽자식 = 삭제(삭제값, 노드.왼쪽자식) return 노드 elif 삭제값 > 노드.값 : 노드.오른쪽자식 = 삭제(삭제값, 노드.오른쪽자식) return 노드 elif 삭제값 == 노드.값 : if 노드.왼쪽자식 is None : return 노드.오른쪽자식 elif 노드.오른쪽자식 if None : return 노드.왼쪽자식 else : 노드.오른쪽자식 = lift(노드.오른쪽자식, 노드) return 노드 def lift(노드, 삭제노드) : if 노드.왼쪽자식 : 노드.왼쪽자식 = lift(노드.왼쪽자식, 삭제노드) return 노드 else : 삭제노드.값 = 노드.값 return 노드.오른쪽자식
- Javascript 코드
function del(node, data){ if(node == null){ return null; } if(data == node.data){ if(node.left == null && node.right == null){ return null; } if(node.left == null){ return node.right; } if(node.right == null){ return node.left; } //자식노드가 2개일 때 let tmp = this.delNode(node.right); //오른쪽 노드에서 가장 작은 값 가져오기 node.data = tmp.data; node.right = this.del(node.right, tempNode.data); return node; } else if(data < node.data){ node.left = this.del(node.left, data); return node; } else{ node.right = this.del(node.right, data); return node; } } function delNode(node){ let temp2 = node; while(tmp2.left !== null){ tmp2 = tmp2.left; } return tmp2; }
- Java 전체 코드 (삽입, 삭제)
public class Tree { static Node root; public static void main(String[] args) { Tree tree = new Tree(); root = null; tree.insertNode(3); tree.insertNode(80); tree.insertNode(19); tree.insertNode(8); tree.insertNode(24); tree.insertNode(6); tree.insertNode(31); tree.insertNode(2); System.out.println("삽입 결과"); tree.display(root); tree.deleteNode(31); tree.deleteNode(8); tree.deleteNode(19); System.out.println(""); System.out.println("삭제 결과"); tree.display(root); } // 출력은 중위순회 public void display(Node node) { if (node != null) { display(node.left); System.out.print(node.data + " "); display(node.right); } } public void insertNode(int key) { Node newNode = new Node(key); if (root == null) { root = newNode; return; } Node current = root; Node prev = null; while (current != null) { prev = current; if (key == current.data) { return; } else if (key < current.data) { current = current.left; } else { current = current.right; } } if (key < prev.data) { prev.left = newNode; } else { prev.right = newNode; } } public void deleteNode (int key) { Node prev = null; Node current = root; while (current != null && current.data != key) { prev = current; if (key < current.data) { current = current.left; } if (key > current.data) { current = current.right; } } if (current == null) { System.out.println("data is not in the tree"); return; } // 단말노드인 경우 if (current.left == null && current.right == null) { if (prev != null) { if (prev.left == current) { prev.left = null; } else { prev.right = null; } } else { root = null; } } // 하나의 자식만 가지는 경우 else if (current.left == null || current.right == null) { Node child = (current.left != null) ? current.left : current.right; if (prev != null) { if (prev.left == current) { prev.left = child; } else { prev.right = child; } } else { root = child; } } // 두 개의 자식을 가지는 경우 else { // 오른쪽 서브트리에서 successor를 찾는다. Node succ_p = current; Node succ = current.right; while (succ.left != null) { succ_p = succ; succ = succ.left; } if (succ_p.left == succ) { succ_p.left = succ.right; } else { succ_p.right = succ.right; } current.data = succ.data; current = succ; } } class Node { int data; Node left; Node right; public Node(int data) { this.data = data; this.left = null; this.right = null; } } }
그 외
레드블랙트리
값이 루트 노드보다 작거나 큰 값만 들어올 경우 → 불균형한 이진탐색트리가 된다. 그러면 검색 효율이 저하된다.

이를 보완해 주는 것이 레드블랙트리 이다.

- 트리의 모든 노드는 검정색과 빨간색이다
- 루트 노드는 항상 검정색이다
- 모든 잎 노드는 검정색 이다
- 빨간색의 노드 자식들은 모두 검정색이지만, 검정색 노드 자식들은 검정색, 빨간색 모두 사용된다.
- 루트 노드에서 모든 잎 노드 사이에 있는 검정색 노드의 수는 모두 동일하다.