[비선형] 2. 이진 트리




1. 이진 트리 (Binary Tree)

  • 각각의 노드가 최대 두개의 자식 노드를 가지는 트리 자료 구조


활용 방식

  • 검색과 정렬 : 이진 탐색 트리와 이진 힙 구현에 활용
  • 허프만 코딩 : 연관 분기 구조를 위한 데이터 표현에 활용


2. 이진 트리의 종류

  • 포화 이진 트리 (Perfect binary tree)
  • 완전 이진 트리 (Complete binary tree)
  • 정 이진 트리 (Full binary tree)
  • 편향 이진 트리 (Skewed binary tree)
  • 균형 이진 트리 (Balanced binary tree)



포화 이진 트리 (Perfect binary tree)

트리

  • 모든 레벨의 노드가 가득 채워져 있는 트리
  • Leaf 노드를 제외한 모든 자식은 2개의 노드를 보유
  • 노드의 개수 : n = 2(h제곱) - 1


완전 이진 트리 (Complete binary tree)

트리

  • 마지막 레벨 전까지 노드가 가득 채워져 있고, 마지막 레벨은 왼쪽부터 순차적으로 채워져 있는 트리
  • 배열을 사용해 효율적인 표현이 가능
  • 노드의 개수 : n < 2(h제곱) - 1


정 이진 트리 (Full binary tree)

트리

  • 모든 노드가 0개 또는 2개의 자식 노드만 갖는 트리
  • proper 또는 plane 이진 트리라고도 불림
  • 노드의 개수 : n <= 2(h제곱) - 1


편향 이진 트리 (Skewed binary tree)

트리

  • 왼쪽 혹은 오른쪽으로 펴향되게 치우쳐 있는 트리
  • 각각의 높이에 하나의 노드만 존재
  • 노드의 개수 : h


균형 이진 트리 (Balance binary tree)

트리

  • 삽입/삭제가 이루어 질 때, 왼쪽 서브 트리와 오른쪽 서브 트리의 높이 차를 1 이하로 맞추는 이진 타맥 트리
  • 서브 트리 높이 차이가 항상 1 이하로 유지
  • 균형 트리 종류 : AVL 트리, Red-Black 트리, B 트리, B+ 트리, B* 트리



3. 이진 트리 순회 (Binary Tree Traversal)

  • 각각의 노드가 최대 두개의 자식 노드를 가지는 트리 자료 구조를 순회하는 방법
  • 노드 추가 : BinaryTree._insertNode(), BinaryTree.insert()
  • 전위 순회 : BinaryTree._preOrderTraverseNode(), BinaryTree.preOrderTraverse()
  • 중위 순회 : BinaryTree._inOrderTraverseNode(), BinaryTree.inOrderTraverse()
  • 후위 순회 : BinaryTree._postOrderTraverseNode(), BinaryTree.postOrderTraverseNode()
  • 층별 순회 : BinaryTree.levelOrderTraverse()

_붙인 애들은 내부적으로 private 으로 쓰인다는 의미 (타언어에서 자주 사용되지만 js 는 해당 안된다) > 다른 메소드에서 호출되는 메소드



4. 이진 트리 구현하기

트리

생성자, _insertNode(), insert()

// Node() : value와 left, right node 저장을 위한 생성자
function Node(value) {
    this.value = value
    this.left = null
    this.right = null
}

// BinaryTree() : 시작 노드인 root 를 저장하기 위한 생성자
function BinaryTree() {
    this.root = null
}

// _insertNode() : 재귀로 트리를 순회하며 노드 추가 (내부 사용)
BinaryTree.prototype._insertNode = function(node, value){
    // 현재값과 비교
    // 작으면 왼쪽, 크면 오른쪽
    // 왼쪽, 오른쪽에 left, right pointer 비어있다면 추가할 노드를 연결
    // 비어있지 않다면 하위 노드에서 다시 비교하도록 넘겨준다

    if (node === null){
        node = new Node(value)
    } else if (value < node.value){
        //🤔 재귀는 어려워...
        node.left = this._insertNode(node.left, value)
    } else if (value > node.value){
        //🤔
        node.right = this._insertNode(node.right, value)
    }

    return node
}

// insert() : 노드 추가
BinaryTree.prototype.insert = function(value){
    this.root = this._insertNode(this.root, value)
}

let tree = new BinaryTree()

tree.insert("F")
tree.insert("B")
tree.insert("A")
tree.insert("D")
tree.insert("C")
tree.insert("E")
tree.insert("G")
tree.insert("I")
tree.insert("H")

console.log(tree)
// BinaryTree {
//     root: Node {
//       value: 'F',
//       left: Node { value: 'B', left: [Node], right: [Node] },
//       right: Node { value: 'G', left: null, right: [Node] }
//     }
//   }




전위 순회 구현

// _preOrderTraverse() : 재귀로 트리를 순회하며 전위 순회 (내부 사용)
BinaryTree.prototype._preOrderTraverse = function(node, callback){
    if (node === null){
        return
    }

    callback(node)
    this._preOrderTraverse(node.left, callback)
    this._preOrderTraverse(node.right, callback)
}

// preOrderTraverse() : 전위 순회하며 노드 출력
BinaryTree.prototype.preOrderTraverse = function(callback){
    this._preOrderTraverse(this.root, callback)
}


let tree = new BinaryTree()

tree.insert("F")
tree.insert("B")
tree.insert("A")
tree.insert("D")
tree.insert("C")
tree.insert("E")
tree.insert("G")
tree.insert("I")
tree.insert("H")

// callback
function printNode(node){
    process.stdout.write(`${node.value} -> `)
}

tree.preOrderTraverse(printNode)
console.log("end")
//F -> B -> A -> D -> C -> E -> G -> I -> H -> end



중위, 후위 순회 구현

// _inOrderTraverse() : 재귀로 트리를 순회하며 중위 순회 (내부 사용)
BinaryTree.prototype._inOrderTraverse = function(node, callback){
    if (node === null){
        return
    }

    this._inOrderTraverse(node.left, callback)
    callback(node)
    this._inOrderTraverse(node.right, callback)
}

// inOrderTraverse() : 중위 순회하며 노드 출력
BinaryTree.prototype.inOrderTraverse = function(callback){
    this._inOrderTraverse(this.root, callback)
}

// _postOrderTraverse() : 재귀로 트리를 순회하며 후위 순회 (내부 사용)
BinaryTree.prototype._postOrderTraverse = function(node, callback){
    if (node === null){
        return
    }

    this._postOrderTraverse(node.left, callback)
    this._postOrderTraverse(node.right, callback)
    callback(node)
}

// postOrderTraverse() : 후위 순회하며 노드 출력
BinaryTree.prototype.postOrderTraverse = function(callback){
    this._postOrderTraverse(this.root, callback)
}


let tree = new BinaryTree()

tree.insert("F")
tree.insert("B")
tree.insert("A")
tree.insert("D")
tree.insert("C")
tree.insert("E")
tree.insert("G")
tree.insert("I")
tree.insert("H")

// callback
function printNode(node){
    process.stdout.write(`${node.value} -> `)
}

console.log("---------전위---------")
tree.preOrderTraverse(printNode)
console.log("end")
//F -> B -> A -> D -> C -> E -> G -> I -> H -> end

console.log("---------중위---------")
tree.inOrderTraverse(printNode)
console.log("end")

console.log("---------후위---------")
tree.postOrderTraverse(printNode)
console.log("end")

// ---------전위---------
// F -> B -> A -> D -> C -> E -> G -> I -> H -> end
// ---------중위---------
// A -> B -> C -> D -> E -> F -> G -> H -> I -> end
// ---------후위---------
// A -> C -> E -> D -> B -> H -> I -> G -> F -> end



층별 순회 구현하기

// Queue 객체 추가
function Queue(array) {
  this.array = array ? array : [];
}

Queue.prototype.isEmpty = function () {
  return this.array.length == 0;
};

Queue.prototype.enqueue = function (element) {
  return this.array.push(element);
};

Queue.prototype.dequeue = function () {
  return this.array.shift();
};

// levelOrderTraverse() : 층별 순회하며 노드 출력
BinaryTree.prototype.levelOrderTraverse = function (callback) {
  let q = new Queue();
  let node;

  q.enqueue(this.root);
  while (!q.isEmpty()) {
    node = q.dequeue();
    callback(node);
    if (node.left !== null) q.enqueue(node.left);
    if (node.right !== null) q.enqueue(node.right);
  }
};

let tree = new BinaryTree();

tree.insert("F");
tree.insert("B");
tree.insert("A");
tree.insert("D");
tree.insert("C");
tree.insert("E");
tree.insert("G");
tree.insert("I");
tree.insert("H");

// callback
function printNode(node) {
  process.stdout.write(`${node.value} -> `);
}

console.log("---------레벨---------");
tree.levelOrderTraverse(printNode);
console.log("end");
//---------레벨---------
//F -> B -> G -> A -> D -> I -> C -> E -> H -> end