🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
Go

Go(golang)は、Googleで開発されたオープンソースのプログラミング言語です。

アルゴリズム

アルゴリズムとは、定められた目的を達成するために、プログラムの理論的な動作を定義するものです。

データ構造

データ構造とは、データの集まりをコンピュータの中で効果的に扱うために、一定の形式に系統立てて格納する形式を指します。(配列/連想配列/木構造など)

ループ

ループとは、プログラミングにおいて、条件に合致している間、複数回繰り返し実行される箇所や、その制御構造を指します

関数

関数(ファンクション・メソッド・サブルーチンとも呼ばれる)は、はプログラムのコードの一部であり、ある特定のタスクを処理するように設計されたものです。

Q&A

解決済

1回答

1343閲覧

Golangにおけるバイナリーツリー(BST) の再帰の仕方について

huskies

総合スコア2

Go

Go(golang)は、Googleで開発されたオープンソースのプログラミング言語です。

アルゴリズム

アルゴリズムとは、定められた目的を達成するために、プログラムの理論的な動作を定義するものです。

データ構造

データ構造とは、データの集まりをコンピュータの中で効果的に扱うために、一定の形式に系統立てて格納する形式を指します。(配列/連想配列/木構造など)

ループ

ループとは、プログラミングにおいて、条件に合致している間、複数回繰り返し実行される箇所や、その制御構造を指します

関数

関数(ファンクション・メソッド・サブルーチンとも呼ばれる)は、はプログラムのコードの一部であり、ある特定のタスクを処理するように設計されたものです。

0グッド

1クリップ

投稿2021/02/26 08:31

編集2021/02/26 09:11

Golangのバイナリーツリー(BST)における、再帰(Recursion) のやり方について疑問があり質問させていただきます。例えば以下のようなバイナリーツリーが構成されており、こちらを再帰を利用することで最小値~最大値を順番とするスライスを生成するとします。
(この場合であれば:[1, 2, 5, 5, 10, 15, 22]が正解)

10 / \ 5 15 / \ \ 2 5 22 / 1

コードとしては、以下のように記述することで求めるアウトプットを出せるのですが、なぜこちらのコードが成り立つのかが良く理解できていません。
例えば、まず if tree.Left != nilを通してnodeを1まで進め(リーフノード)、そのループ時はnilとなるのでappendされる点までは分かりました。ただ、その後、if tree.Right !=nilに進むと思うのですが、この時点で treeは1上にあるため、tree.Right == nilを返してしまい、関数が終了してしまうのではという理解をしています。

疑問点の仮説をまとめさせていただきました。

  • 1のリーフノードに届くまで3回再帰しています。例えば初回の再帰が呼ばれると、ノード自体は5に移動すると思いますが、この時点では tree.Left != nil のif文に入るため、appendまでは行かず、一度ここで止まり(?)処理待ちの状態となる
  • そして, 1に到達すると、if文には入らないため、append, 次のif分を処理し、一度return(?)する
  • その後、処理が止まっていた再帰の処理が新しい順から始まる

おそらくこちらの流れなのだと思うのですが、再帰をする際にif文にはまると処理待ちの状態になるのか・1に到達してreturnすると処理待ちがなぜ再開するのかが、あまりスッキリしていません。加えて、現状の理解で間違っている点がありましたらご指摘いただけると幸いです。よろしくお願い致します。

Go

1 2type BST struct { 3 Value int 4 5 Left *BST 6 Right *BST 7} 8 9func (tree *BST) InOrderTraverse(array []int) []int { 10 if tree.Left != nil { 11 array = tree.Left.InOrderTraverse(array) 12 } 13 14 array = append(array, tree.Value) 15 if tree.Right != nil { 16 array = tree.Right.InOrderTraverse(array) 17 } 18 return array 19} 20

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

hoshi-takanori

2021/02/26 08:33

ループじゃなくて再帰ですね。
huskies

2021/02/26 08:34

ご指摘ありがとうございます。修正いたします
hoshi-takanori

2021/02/26 08:43

「この時点で treeは`1`上にあるため、`tree.Right == nil`を返してしまい、関数が終了してしまうのではという理解をしています。」の部分ですが、関数が 1 回終了するのは確かですが、それまでに何回呼ばれたかを考えましょう。
huskies

2021/02/26 09:13

ありがとうございます。投稿を修正し、疑問点をまとめさせて頂きました。具体的には、なぜ処理が一度止まるのか、関数が一度終了したのち、それまでに呼ばれた処理待ちが動き出すフローが何となく分かるのですが、まだ曖昧でご説明いただければ幸いです。
guest

回答1

0

ベストアンサー

1つずつ回答します。

huskiesさんの理解について

例えば、まずif tree.Left != nilを通してnodeを1まで進め(リーフノード)、そのループ時はnilとなるのでappendされる

不明瞭なので、書き直すと下記の通りです。

if tree.Left != nilを通して、10, 5, 2のノードにおいてtree.Left.InOrderTraverse(array)が呼び出され、最終的に1のノードでtree.Leftがnilになるので、array = append(array, tree.Value)が実行される。ここで重要なのは、10, 5, 2のノードでの呼び出しはリターンされず、スタックに積まれたままである(実行中)ということです。そのため、1のノードの実行が終了してもその後2のノードが引き続き実行されます。

したがって、その後の記述で、

関数が終了してしまうのでは

と書かれていますが、1のノードに生えているメソッドInOrderTraverseについて、この理解は正しいです。
(本質ではないですが、InOrderTraverseはレシーバに生えているので関数ではなくメソッドです。)

huskiesさんの仮説について

1のリーフノードに届くまで3回再帰しています。例えば初回の再帰が呼ばれると、ノード自体は5に移動すると思いますが、この時点では tree.Left != nil のif文に入るため、appendまでは行かず、一度ここで止まり(?)処理待ちの状態となる

正しいです。

そして, 1に到達すると、if文には入らないため、append, 次のif分を処理し、一度return(?)する

正しいです。

その後、処理が止まっていた再帰の処理が新しい順から始まる

「新しい順から始まる」という言葉を明確にすると、「2のノードのarray = tree.Left.InOrderTraverse(array)の代入が実行される」です。

huskiesさんの質問について

下記の加算結果を代入するコードが実行される時を考えます。

go

1func main() { 2 sum := 1 + 2 3 fmt.Println(sum) 4}

詳細に書くと長くなるので、概要だけ掻い摘むと

  1. 1 + 2が評価される
  2. 1 + 2の評価した結果をsumに代入する

という2つの過程に分けられます。
ここで、1 + 2の評価が終わるまで代入が為されないことが分かると思います。

次に、下記の関数呼び出し結果を代入するコードが実行される時を考えます。

go

1func One() int { 2 return 1 3} 4 5func main() { 6 result := One() 7 fmt.Println(result) 8}

この場合も同様に、

  1. One()が評価される
  2. One()を評価した結果をresultに代入する

という2つの過程に分けられます。
ここで、One()の評価が終わるまで代入が為されないことが分かります。

これをInOrderTraverse(array)に置き換えて考えてください。
もう説明は要らないでしょう。

補足

実際に実行される順番を見るために、Playgroundのコードを開いて、Runして得られた結果を見てください。すると、呼び出される順番が分かります。

このように、実行順序を知るために各行で結果を出力するのは良く使う手法です。他には、gdbなどのデバッガで処理手順を知るのも1つの手段だと思います。詳しくはググってください。

一応ソースコードと結果を載せておきます。

go

1package main 2 3import "fmt" 4 5type BSTNode struct { 6 Value int 7 8 Left *BSTNode 9 Right *BSTNode 10} 11 12func (tree *BSTNode) Traverse(array []int) []int { 13 fmt.Printf("[Called ] array: %v, now: %d\n", array, tree.Value) 14 // 左のノードがあれば、そちらのノードで更にトラバースする 15 if tree.Left != nil { 16 fmt.Printf("[CallLeft ] array: %v, now: %d\n", array, tree.Value) 17 array = tree.Left.Traverse(array) 18 fmt.Printf("[CalledLeft ] array: %v, now: %d\n", array, tree.Value) 19 } 20 21 // arrayに今見ているノードの値を追加する 22 array = append(array, tree.Value) 23 // 右にノードがあれば、そちらのノードで更にトラバースする 24 if tree.Right != nil { 25 fmt.Printf("[CallRight ] array: %v, now: %d\n", array, tree.Value) 26 array = tree.Right.Traverse(array) 27 fmt.Printf("[CalledRight] array: %v, now: %d\n", array, tree.Value) 28 } 29 fmt.Printf("[Return ] array: %v, now: %d\n", array, tree.Value) 30 // トラバースした結果を返す 31 return array 32} 33 34// ※バックスラッシュが出せなかったので、\で代用しています 35// 10 36// / \ 37// 5 15 38// / \ \ 39// 2 6 22 40// / 41// 1 42 43func main() { 44 // ノードを作る 45 node1 := &BSTNode{Value: 1, Left: nil, Right: nil} 46 node2 := &BSTNode{Value: 2, Left: node1, Right: nil} // node1というノードを左の子として保持している(以下略) 47 node6 := &BSTNode{Value: 6, Left: nil, Right: nil} 48 node5 := &BSTNode{Value: 5, Left: node2, Right: node6} 49 node22 := &BSTNode{Value: 22, Left: nil, Right: nil} 50 node15 := &BSTNode{Value: 15, Left: nil, Right: node22} 51 node10 := &BSTNode{Value: 10, Left: node5, Right: node15} 52 53 // 根からトラバースし始める 54 fmt.Println(node10.Traverse([]int{})) 55 56 // BITの格納順序が壊れていた場合は、最小値~最大値に出力されない 57 // この場合は5と6を反転させている 58 node5 = &BSTNode{Value: 5, Left: nil, Right: nil} 59 node6 = &BSTNode{Value: 6, Left: node2, Right: node5} 60 node10 = &BSTNode{Value: 10, Left: node6, Right: node15} 61 // 根からトラバースし始める 62 fmt.Println(node10.Traverse([]int{})) 63 64} 65 66 67// Output: 68// [Called ] array: [], now: 10 69// [CallLeft ] array: [], now: 10 70// [Called ] array: [], now: 5 71// [CallLeft ] array: [], now: 5 72// [Called ] array: [], now: 2 73// [CallLeft ] array: [], now: 2 74// [Called ] array: [], now: 1 75// [Return ] array: [1], now: 1 76// [CalledLeft ] array: [1], now: 2 77// [Return ] array: [1 2], now: 2 78// [CalledLeft ] array: [1 2], now: 5 79// [CallRight ] array: [1 2 5], now: 5 80// [Called ] array: [1 2 5], now: 6 81// [Return ] array: [1 2 5 6], now: 6 82// [CalledRight] array: [1 2 5 6], now: 5 83// [Return ] array: [1 2 5 6], now: 5 84// [CalledLeft ] array: [1 2 5 6], now: 10 85// [CallRight ] array: [1 2 5 6 10], now: 10 86// [Called ] array: [1 2 5 6 10], now: 15 87// [CallRight ] array: [1 2 5 6 10 15], now: 15 88// [Called ] array: [1 2 5 6 10 15], now: 22 89// [Return ] array: [1 2 5 6 10 15 22], now: 22 90// [CalledRight] array: [1 2 5 6 10 15 22], now: 15 91// [Return ] array: [1 2 5 6 10 15 22], now: 15 92// [CalledRight] array: [1 2 5 6 10 15 22], now: 10 93// [Return ] array: [1 2 5 6 10 15 22], now: 10 94[1 2 5 6 10 15 22] 95// [Called ] array: [], now: 10 96// [CallLeft ] array: [], now: 10 97// [Called ] array: [], now: 6 98// [CallLeft ] array: [], now: 6 99// [Called ] array: [], now: 2 100// [CallLeft ] array: [], now: 2 101// [Called ] array: [], now: 1 102// [Return ] array: [1], now: 1 103// [CalledLeft ] array: [1], now: 2 104// [Return ] array: [1 2], now: 2 105// [CalledLeft ] array: [1 2], now: 6 106// [CallRight ] array: [1 2 6], now: 6 107// [Called ] array: [1 2 6], now: 5 108// [Return ] array: [1 2 6 5], now: 5 109// [CalledRight] array: [1 2 6 5], now: 6 110// [Return ] array: [1 2 6 5], now: 6 111// [CalledLeft ] array: [1 2 6 5], now: 10 112// [CallRight ] array: [1 2 6 5 10], now: 10 113// [Called ] array: [1 2 6 5 10], now: 15 114// [CallRight ] array: [1 2 6 5 10 15], now: 15 115// [Called ] array: [1 2 6 5 10 15], now: 22 116// [Return ] array: [1 2 6 5 10 15 22], now: 22 117// [CalledRight] array: [1 2 6 5 10 15 22], now: 15 118// [Return ] array: [1 2 6 5 10 15 22], now: 15 119// [CalledRight] array: [1 2 6 5 10 15 22], now: 10 120// [Return ] array: [1 2 6 5 10 15 22], now: 10 121// [1 2 6 5 10 15 22]

要点だけを話したので、更に詳しく知りたい場合は「関数 呼び出し 仕組み」や「再帰 スタックメモリ」などで調べると良いと思います。ここら辺はコンピュータサイエンスの分野になるので、「実行できれば良い」というスタンスなら別に知る必要はないと思います。

長くなりましたが、プログラムが実行される時にどのように実行されるかを理解するのは非常に重要で為になります。引き続き頑張ってください。

投稿2021/02/26 18:26

編集2021/02/26 18:30
task4233

総合スコア106

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問