🔥 연습문제 - 트리의 동치성 판별하기
트리는 같은 값을 저장하고 있어도 구조가 다를 수 있습니다. 아래 그림은 1, 1, 2, 3, 5, 8, 13이라는 같은 값의 sequence를 저장하고 있지만 서로 다른 구조를 가진 두 개의 이진 트리를 보여주고 있죠.
대부분의 언어에서 두 이진 트리가 같은 값의 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
를 이용해 두 트리 t1
과 t2
가 같은 값을 저장하고 있는지 판별해야 합니다. 다음은 그 구현 예시입니다.
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
위 코드는 t1
과 t2
를 위한 별도의 채널 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