🔥 연습문제 - 트리의 동치성 판별하기

615자
6분

트리는 같은 값을 저장하고 있어도 구조가 다를 수 있습니다. 아래 그림은 1, 1, 2, 3, 5, 8, 13이라는 같은 값의 sequence를 저장하고 있지만 서로 다른 구조를 가진 두 개의 이진 트리를 보여주고 있죠.

lecture image

대부분의 언어에서 두 이진 트리가 같은 값의 sequence를 저장하고 있는지 확인하는 함수를 작성하는 것은 꽤나 복잡한 일이 될 수 있습니다. 하지만 Go의 동시성과 채널을 이용하면 간단한 해법을 만들어낼 수 있습니다.

먼저 tree 패키지를 사용할 건데, 이 패키지에는 아래와 같은 Tree 구조체가 정의되어 있습니다.

type Tree struct {
    Left  *Tree
    Value int
    Right *Tree
}
 
go

Walk 함수 구현하기

Walk 함수는 트리 t를 순회하면서 모든 값을 채널 ch로 보내야 합니다. 아래는 Walk 함수를 구현한 예시 코드입니다.

func Walk(t *tree.Tree, ch chan int) {
    var walker func(t *tree.Tree) // 재귀 함수를 위한 내부 함수 변수 선언
    walker = func(t *tree.Tree) {
        if t == nil { // 기저 조건: nil 노드일 경우 그냥 리턴
            return
        }
        walker(t.Left) // 왼쪽 서브트리 먼저 순회
        ch <- t.Value // 현재 노드의 값을 채널로 전송
        walker(t.Right) // 오른쪽 서브트리 순회
    }
    walker(t) // 트리 순회 시작
    close(ch) // 순회 끝난 후 채널 닫기
}
 
go

위 코드는 재귀를 이용해 트리를 중위순회(in-order traversal)합니다. 왼쪽 서브트리를 먼저 순회한 뒤 현재 노드의 값을 채널로 보내고, 마지막으로 오른쪽 서브트리를 순회하는 방식이죠.

Walk 함수 테스트하기

tree.New(k)k, 2k, 3k, ..., 10k의 값을 갖는 random한 구조의 이진 탐색 트리를 만들 수 있습니다. 채널 ch를 만들고 고루틴으로 Walk를 실행한 뒤, 채널에서 10개의 값을 읽어 출력해 봅시다. 1부터 10까지의 숫자가 출력될 거예요.

func main() {
    ch := make(chan int)
    go Walk(tree.New(1), ch)
    for i := 0; i < 10; i++ {
        fmt.Println(<-ch)
    }
}
 
go

Same 함수 구현하기

Same 함수는 Walk를 이용해 두 트리 t1t2가 같은 값을 저장하고 있는지 판별해야 합니다. 다음은 그 구현 예시입니다.

func Same(t1, t2 *tree.Tree) bool {
    ch1, ch2 := make(chan int), make(chan int)
    go Walk(t1, ch1)
    go Walk(t2, ch2)
    for {
        v1, ok1 := <-ch1
        v2, ok2 := <-ch2
        if !ok1 || !ok2 { // 어느 한쪽 채널이라도 닫힌 경우
            return ok1 == ok2 // 둘 다 닫혔으면 true, 아니면 false
        }
        if v1 != v2 { // 값이 다르면 false 리턴
            return false
        }
    }
}
 
go

위 코드는 t1t2를 위한 별도의 채널 ch1, ch2를 만들고 각각의 트리를 Walk 함수로 순회시킵니다. 그런 다음 무한 루프를 돌면서 두 채널에서 값을 하나씩 꺼내 비교하는데, 만약 값이 다르면 바로 false를 리턴하고, 어느 한쪽 채널이라도 닫힌 경우엔 둘 다 닫혔는지 여부에 따라 true 또는 false를 리턴하게 됩니다.

Same 함수 테스트하기

Same(tree.New(1), tree.New(1))true를, Same(tree.New(1), tree.New(2))false를 리턴해야겠죠? 아래 코드로 테스트해 봅시다.

func main() {
    fmt.Println(Same(tree.New(1), tree.New(1))) // true
    fmt.Println(Same(tree.New(1), tree.New(2))) // false
}
 
go

이렇게 Go의 동시성과 채널을 활용하니 트리 동치성 판별 문제를 꽤 간단하고 우아하게 해결할 수 있었습니다. 고루틴이 트리를 동시에 순회하고 채널로 값을 전달하는 방식이 인상적이었어요.

전체 코드를 정리하면 다음과 같습니다.

package main
 
import (
    "fmt"
 
    "golang.org/x/tour/tree"
)
 
// Walk walks the tree t sending all values
// from the tree to the channel ch.
func Walk(t *tree.Tree, ch chan int) {
    var walker func(t *tree.Tree)
    walker = func(t *tree.Tree) {
        if t == nil {
            return
        }
        walker(t.Left)
        ch <- t.Value
        walker(t.Right)
    }
    walker(t)
    close(ch)
}
 
// Same determines whether the trees
// t1 and t2 contain the same values.
func Same(t1, t2 *tree.Tree) bool {
    ch1, ch2 := make(chan int), make(chan int)
    go Walk(t1, ch1)
    go Walk(t2, ch2)
    for {
        v1, ok1 := <-ch1
        v2, ok2 := <-ch2
        if !ok1 || !ok2 {
            return ok1 == ok2
        }
        if v1 != v2 {
            return false
        }
    }
}
 
func main() {
    ch := make(chan int)
    go Walk(tree.New(1), ch)
    for i := 0; i < 10; i++ {
        fmt.Println(<-ch)
    }
 
    fmt.Println(Same(tree.New(1), tree.New(1)))
    fmt.Println(Same(tree.New(1), tree.New(2)))
}
 
go