Trees

Array, LinkedList, Stack, Queue 와 달리 일직선 구조가 아니라 부모 자식 관계를 가지는 구조

Tree 의 종류

Binary Tree (이진 트리)

부모가 두 개의 자식 node 를 갖는 tree

Binary Search Tree (이진 검색 트리)

  • “왼쪽 자식 < 부모 < 오른쪽 자식” 의 관계를 가지는 이진 트리
  • 검색에 특화됨
  • search 의 시간복잡도 : O(log N)

Tries (트라이)

  • 어떠한 텍스트에서 문자열이 존재하는지 검색하는데에 최적화된 트리
  • 기존 BST 에서는 각각의 node 에서 문자열을 또 하나씩 검색해야함
    BST 에서 문자열 search 시간복잡도 : O (M log N)
    (M : 문자열의 최대 길이)
  • Tries 에서 문자열 search 시간복잡도 : O (M) 첫 글자를 반복 저장할 필요가 없어 공간 효율도 좋다. image

Balance

root node 를 기준으로 왼쪽과 오른쪽 자식들의 균형이 어느정도 맞는 tree
대표적인 Balanced Binary Tree : - red-black tree - AVL tree

Complete Binary Tree (완전 이진 트리)

root node 를 기준으로 leaf level (마지막 레벨) 을 제외한 왼쪽과 오른쪽 자식들의 level 수가 동일해야하고
leaf level 은 왼쪽부터 차례대로 채워져 있는 이진 트리

Full Binary Tree

하나의 자식 node 만 가지고 있는 부모 node 가 없는 이진 트리 (자식 0 또는 2 개)

Perfect Binary Tree

모든 부모 node 가 모두 두개의 자식 node 를 가지고 있고 level 도 동일 (완벽한 피라미드 형태)

Binary Tree 의 순회 방법 3가지

  1. Inorder 중위

    • left -> root -> right 순서 (left 의 최 하단 leaf node 부터 시작한다.)
  2. Preorder 전위

    • root -> left -> right 순서 (root node 부터 시작)
  3. Postorder 후위

    • left -> right -> root 순서 (left 의 최 하단 leaf node 부터 시작한다.)
class Node {
  constructor(data, left, right) {
    this.data = data;
    this.left = left;
    this.right = right;
    return this;
  }
}

class BinarySearchTree {
  constructor() {
    this.root = null;
  }

  setRoot(data) {
    this.root = new Node(data, null, null);
    return this.root;
  }
  getRoot() {
    return this.root;
  }
  insert(data) {
    const newNode = new Node(data, null, null);
    if (this.root === null) {
      this.root = newNode;
    } else {
      let currentNode = this.root;
      while (true) {
        if (data < currentNode.data) {
          // Left
          if (!currentNode.left) {
            // leaf 까지 내려감
            currentNode.left = newNode;
            return this;
          }
          currentNode = currentNode.left;
        }
        if (data > currentNode.data) {
          // Right
          if (!currentNode.right) {
            // leaf 까지 내려감
            currentNode.right = newNode;
            return this;
          }
          currentNode = currentNode.right;
        }
      }
    }
  }
  /* inorder 순회  left -> root -> right */
  inorder(node = this.root) {
    if (!this.root) return "Tree 가 비어있습니다.";
    if (node !== null) {
      this.inorder(node.left);
      console.log(node.data);
      this.inorder(node.right);
    }
  }
  /* preorder 순회  root -> left -> right */
  preorder(node = this.root) {
    if (!this.root) return "Tree 가 비어있습니다.";
    if (node !== null) {
      console.log(node.data);
      this.preorder(node.left);
      this.preorder(node.right);
    }
  }
  /* postorder 순회  left -> right -> root */
  postorder(node = this.root) {
    if (!this.root) return "Tree 가 비어있습니다.";
    if (node !== null) {
      this.postorder(node.left);
      this.postorder(node.right);
      console.log(node.data);
    }
  }
  /* look up (특정 값 찾기) */
  lookup(data) {
    if (!this.root) return false;
    let currentNode = this.root;
    while (currentNode) {
      if (data < currentNode.data) {
        currentNode = currentNode.left;
      } else if (data > currentNode.data) {
        currentNode = currentNode.right;
      } else if (currentNode.data === data) {
        return currentNode;
      }
    }
    return null;
  }
  remove(data) {
    if (!this.root) {
      return false;
    }
    let currentNode = this.root;
    let parentNode = null;
    while (currentNode) {
      if (data < currentNode.data) {
        parentNode = currentNode;
        currentNode = currentNode.left;
      } else if (data > currentNode.data) {
        parentNode = currentNode;
        currentNode = currentNode.right;
      } else if (currentNode.data === data) {
        //We have a match, get to work!

        //Option 1: No right child:
        if (currentNode.right === null) {
          if (parentNode === null) {
            this.root = currentNode.left;
          } else {
            //if parent > current data, make current left child a child of parent
            if (currentNode.data < parentNode.data) {
              parentNode.left = currentNode.left;

              //if parent < current data, make left child a right child of parent
            } else if (currentNode.data > parentNode.data) {
              parentNode.right = currentNode.left;
            }
          }

          //Option 2: Right child which doesnt have a left child
        } else if (currentNode.right.left === null) {
          currentNode.right.left = currentNode.left;
          if (parentNode === null) {
            this.root = currentNode.right;
          } else {
            //if parent > current, make right child of the left the parent
            if (currentNode.data < parentNode.data) {
              parentNode.left = currentNode.right;

              //if parent < current, make right child a right child of the parent
            } else if (currentNode.data > parentNode.data) {
              parentNode.right = currentNode.right;
            }
          }

          //Option 3: Right child that has a left child
        } else {
          //find the Right child's left most child
          let leftmost = currentNode.right.left;
          let leftmostParent = currentNode.right;
          while (leftmost.left !== null) {
            leftmostParent = leftmost;
            leftmost = leftmost.left;
          }

          //Parent's left subtree is now leftmost's right subtree
          leftmostParent.left = leftmost.right;
          leftmost.left = currentNode.left;
          leftmost.right = currentNode.right;

          if (parentNode === null) {
            this.root = leftmost;
          } else {
            if (currentNode.data < parentNode.data) {
              parentNode.left = leftmost;
            } else if (currentNode.data > parentNode.data) {
              parentNode.right = leftmost;
            }
          }
        }
        return true;
      }
    }
  }
}

const myTree = new BinarySearchTree();
console.log(myTree.setRoot(10));
console.log(myTree.getRoot());
console.log(myTree.insert(5));
console.log(myTree.insert(15));
console.log(myTree.insert(8));
console.log(myTree.insert(3));
console.log(myTree.insert(12));
console.log(myTree.insert(17));
// console.log(JSON.stringify(myTree.insert(17)));
console.log(myTree.lookup(5));
// console.log(myTree.inorder());
// console.log(myTree.preorder());
// console.log(myTree.postorder());