ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Chapter 12: Binary Trees
    Raywenderlich/Data Structures & Algorithms in Swift 2020. 2. 9. 12:20

    자식 노드의 수에 제한이 없는 일반 Tree(트리)와 달리, Binary Tree(이진 트리)는 각 node에 최대 두 개의 자식이 있다. 이 자식들을 각각 위치에 따라 left, right라고 부른다.

     

    이진 트리는 많은 트리 구조 알고리즘의 기초가 된다.

     

    Implementation

    public class BinaryNode<Element> {
        public var value: Element
        public var leftChild: BinaryNode?
        public var rightChild: BinaryNode?
        
        public init(value: Element) {
            self.value = value
        }
    }

    BinaryNode를 사용해 이진트리를 구현한다.

    var tree: BinaryNode<Int> = {
        let zero = BinaryNode(value: 0)
        let one = BinaryNode(value: 1)
        let five = BinaryNode(value: 5)
        let seven = BinaryNode(value: 7)
        let eight = BinaryNode(value: 8)
        let nine = BinaryNode(value: 9)
        
        seven.leftChild = one
        one.leftChild = zero
        one.rightChild = five
        seven.rightChild = nine
        nine.leftChild = eight
        
        return seven
    }() //클로저

     

    Building a diagram

    자료구조(Dats Structure)의 다이어그램을 작성하면, 작동방식을 이해하는 데 도움이 될 수 있다. 콘솔(Console)에서 이진 트리를 시각화하는 데 도움이 되는 재사용 가능한 알고리즘을 구현한다.

    이 알고리즘은 Károly Lrentey의 책, Optimizing Collections에서 가져왔다.
    https://www.objc.io/books/optimizing-collections/
    extension BinaryNode: CustomStringConvertible {
        public var description: String {
            diagram(for: self)
        }
        
        private func diagram(for node: BinaryNode?,
                             _ top: String = "",
                             _ root: String = "",
                             _ bottom: String = "") -> String {
            guard let node = node else {
                return root + "nil\n"
            }
            
            if node.leftChild == nil && node.rightChild == nil {
                //자식 노드가 없는 경우(leaf)
                return root + "\(node.value)\n"
            }
            
            return diagram(for: node.rightChild, top + " ", top + "┌──", top + "│ ")
                + root + "\(node.value)\n"
                + diagram(for: node.leftChild, bottom + "│ ", bottom + "└──", bottom + " ")
        }
    }

    이진 트리 다이어그램(diagram)을 나타내는 문자열을 재귀적으로 생성한다. 위에서 작성한 트리로 확인해 본다.

    example(of: "tree diagram") {
        print(tree)
        //  ┌──nil
        // ┌──9
        // │ └──8
        // 7
        // │ ┌──5
        // └──1
        //  └──0
    }

    출력은 다음과 같다.

        ┌──nil
    ┌──9
    │ └──8
    7
    │ ┌──5
    └──1
        └──0

    해당 알고리즘은 모든 이진트리에서 사용할 수 있다.

     

    Traversal algorithms

    이전의 트리 순회 알고리즘과 유사한 방식으로 구현하되, 몇 가지 수정이 필요하다. in-order traversal(중위 순회), pre-order traversal(전위 순회), post-order traversal(후위 순회)이 있다.

    위키백과 트리 순회
    https://ko.wikipedia.org/wiki/%ED%8A%B8%EB%A6%AC_%EC%88%9C%ED%9A%8C

     

    In-order traversal

    In-order traversal(중위 순회)는 Root에서 시작해 다음과 같은 순서로 순회한다(LPR).

    • 현재 노드에서 왼쪽 자식이 있으면, 재귀적으로 먼저 순회 한다(L).
    • 이후, 기존의 노드를 방문한다(P, 가운데, parent).
    • 현재 노드에서 오른쪽 자식이 있으면, 재귀적으로 순회한다(R).

    위의 트리에서 중위 순회는 다음과 같다.

    트리가 특정 방식으로 구조화되면(이진 탐색 트리(binary search tree )이면), 중위순회는 오름차순으로 출력된다.

    extension BinaryNode {
        public func traverseInOrder(visit: (Element) -> Void) {
            leftChild?.traverseInOrder(visit: visit)
            visit(value)
            rightChild?.traverseInOrder(visit: visit)
            //위의 규칙대로(LPR) 순회를 구현하면 된다.
        }
    }

    가운데 value를 방문하기 전에 왼쪽 node를 먼저 방문 한다. 그런 다음, 중간을 거쳐 오른쪽 node로 이동하면서 순회하면 된다. 재귀적으로 호출되므로, leftChild의 leftChild, leftChild, ... 최하단의 좌측 leaf 까지 가게 된 후, visit(value)가 호출된다.

    example(of: "in-order traversal") {
        tree.traverseInOrder { print($0) }
        // 0
        // 1
        // 5
        // 7
        // 8
        // 9
    }

    출력은 다음과 같다.

    ---Example of in-order traversal---
    0
    1
    5
    7
    8
    9

     

    Pre-order traversal

    Pre-order traversal(전위 순회)는 현재 노드를 먼저 방문한 다음 왼쪽 및 오른쪽 하위 노드를 재귀적으로 방문한다(PLR).

    extension BinaryNode {
        public func traversePreOrder(visit: (Element) -> Void) {
            visit(value)
            leftChild?.traverseInOrder(visit: visit)
            rightChild?.traverseInOrder(visit: visit)
            //PLR
        }
    }

    구현을 확인해 본다.

    example(of: "pre-order traversal") {
        tree.traversePreOrder { print($0) }
        // 7
        // 0
        // 1
        // 5
        // 8
        // 9
    }

    출력은 다음과 같다.

    ---Example of pre-order traversal---
    7
    1
    0
    5
    9
    8

     

    Post-order traversal

    Post-order traversal(후위 순회)는 왼쪽 및 오른쪽 노드를 재귀적으로 방문한 후, 현재 노드를 방문한다(LRP).

    즉, 해당 노드는 자신을 방문하기 전에 자식 노드를 먼저 순회한다. 따라서 Root 노드를 항상 마지막에 방문한다.

    extension BinaryNode {
        public func traversePostOrder(visit: (Element) -> Void) {
            leftChild?.traverseInOrder(visit: visit)
            rightChild?.traverseInOrder(visit: visit)
            visit(value)
            //LRP
        }
    }

    구현을 확인해 본다.

    example(of: "post-order traversal") {
        tree.traversePostOrder { print($0) }
        // 0
        // 5
        // 1
        // 8
        // 9
        // 7
    }

    출력은 다음과 같다.

    ---Example of post-order traversal---
    0
    5
    1
    8
    9
    7

    이러한 순회 알고리즘은 각각 시간과 공간 복잡도가 모두 O(n) 이다. 이진 탐색 트리(Binary Search Tree)를 중위 순회하면 오름차순으로 출력할 수 있다.

     

    Key points

    • 이진 트리(binary tree)는 가장 중요한 트리 구조의 기초가 된다. 이진 검색 트리(Binary search tree)와 AVL 트리(AVL tree)는 삽입과 삭제에 제한을 주는 이진트리이다.
    • in-order traversal(중위 순회), pre-order traversal(전위 순회), post-order traversal(후위 순회)는 이진 트리에서만 중요한 것이 아니다. 어떤 트리에서든 데이터를 처리한다면, 순회를 자주 사용하게 된다.

    'Raywenderlich > Data Structures & Algorithms in Swift' 카테고리의 다른 글

    Chapter 14: Binary Search Trees  (0) 2020.02.26
    Chapter 13: Binary Tree Challenges  (0) 2020.02.10
    Chapter 11: Tree Challenges  (0) 2020.02.08
    Chapter 10: Trees  (0) 2020.02.07
    Chapter 9: Queue Challenges  (0) 2020.02.05
Designed by Tistory.