Conversation
| return tree.valid | ||
| } | ||
|
|
||
| func isValidBSTRecursively(root *TreeNode) subtree { |
There was a problem hiding this comment.
些末なことかも知れないですが、返り値が boolean 値出ないのに接頭辞が is になっているのが気になりました。
There was a problem hiding this comment.
なんと、本当ですね
subtree構造体のvaidフィールドが何かということをメインに調べたいので、checkSubtreeRecursivelyみたいな感じの方がいいですかね
| - 標準ライブラリslicesのIsSortedFuncの第二引数の関数の引数が、 | ||
| a = s[i], b = s[i+1]の順に並んでいると思ったら逆だった。 | ||
| なぜ逆順なのだろう? | ||
| pkg.go.devのドキュメントに特に何も注意書きがなく、内部実装を見て初めて気がついた |
There was a problem hiding this comment.
IsSorted の気持ちは分かります。sorted な配列に追加することがあるから後ろから見ていきたくて、less のほうが基本的だからこの構造です。Func も合わせたんでしょう。
https://cs.opensource.google/go/go/+/refs/tags/go1.23.2:src/slices/sort.go;drc=6d93de2c110f66457f103c33ba496ff2e2bf33af;l=53
There was a problem hiding this comment.
そういうことだったんですね
そういえばslices.Sortで使われているpattern-defeating quicksortもソートされた配列に最後の要素だけ追加が生じた場合にO(n)でできるアルゴリズムであると以前知りました
https://cs.opensource.google/go/go/+/refs/tags/go1.23.2:src/slices/sort.go;l=16;drc=6d93de2c110f66457f103c33ba496ff2e2bf33af
https://github.com/orlp/pdqsort?tab=readme-ov-file#the-best-case
|
拝見しました。 参考に再帰in-order貼っておきます。 class Solution:
def isValidBST(self, root: Optional[TreeNode]) -> bool:
if not root:
return True
if root.left:
if root.left.val >= root.val:
return False
if not self.isValidBST(root.left):
return False
if root.right:
if root.right.val <= root.val:
return False
if not self.isValidBST(root.right):
return False
return True |
これ、left の下全体が、root.val よりも小さいという制限をチェックしていないんじゃないですか? |
| return true | ||
| } | ||
| ``` | ||
|
|
There was a problem hiding this comment.
Go らしさのあるものをと思ったので chan を使ったコードとかを上げてみます。
func traverseInorderHelper(node *TreeNode, c chan *TreeNode) {
if (node.Left != nil) {
traverseInorderHelper(node.Left, c)
}
c <- node
if (node.Right != nil) {
traverseInorderHelper(node.Right, c)
}
}
func traverseInorder(root *TreeNode, c chan *TreeNode) {
traverseInorderHelper(root, c)
close(c)
}
func isValidBST(root *TreeNode) bool {
c := make(chan *TreeNode)
go traverseInorder(root, c)
var previous *TreeNode = nil
for {
node, ok := <- c
if !ok {
return true
}
if previous != nil && node.Val <= previous.Val {
return false
}
previous = node
}
}There was a problem hiding this comment.
@oda
ありがとうございます。goroutineに慣れていないので確認の質問をさせてください。
このコードの利点はinorderなノード探索の結果をチャンネルに押し出していく形にすることによって、スライスに溜める時と比べてメモリ消費量を抑えられることにある、ということですか?
あともうひとつの利点はgoroutineの並列処理を使っているので、ノード同士の関係性チェックとinorder探索が並列で行われているので、実行時間の短縮が期待される、ということも挙げられると思うのですが、合っていますか?
There was a problem hiding this comment.
それから別の類の質問なのですが、odaさんのコードを見ていると上から子関数->親関数という順序で書いていることが多いのですが、この順序は単に好みですか?styleguideに特に記載はなかったのですが、個人的には親->子という順序で書いたほうが読みやすいので、子->親としている背景があったら知りたいと思った次第です
There was a problem hiding this comment.
あー、メリットとしては複数のスレッドで並列計算されることくらいでしょうが、どちらかというとこれは「言語の機能を見てみよう」くらいの気持ちで書いているので、そんなに真面目に考えなくていいと思います。メモリー消費量は分かりませんが直感的には増えるんじゃないでしょうか。関数呼び出しのスタックが積まれるので。
子供から書くのは、おそらく C/C++ 由来の癖と思います。先に signature がないといけません。どちらでもいいんじゃないでしょうか。
| 役割分担をどう定義するかなんだな、と頭でわかっているつもりだが、 | ||
| いまだに実装の苦手意識がある。 | ||
| 今Haskellを使う授業を受けているので再帰のいい訓練になることを願う | ||
| - 一般的に、末尾再帰はコンパイラの最適化がかかることが多いので、 |
There was a problem hiding this comment.
2ndのコードは、末尾再帰のある言語、処理系でも末尾再帰最適化されないと思います。
returnする際に、
- 左のノードについて評価
- 結果を保存
- 右のノードについて評価
- 最後に&&を評価
という流れで呼び出した結果を呼び出し元で保存する必要があるので、関数呼び出しをジャンプに変換することができないと思います。
There was a problem hiding this comment.
ご指摘ありがとうございます。末尾再帰とその最適化についてよく理解できていなさそうだったので、以下の記事を読んでみました。
https://qiita.com/pebblip/items/cf8d3230969b2f6b3132
https://qiita.com/TakekazuKATO/items/fc3fedf0ab36ff039aaa#%E6%9C%AB%E5%B0%BE%E5%86%8D%E5%B8%B0%E3%81%A8%E6%9C%AB%E5%B0%BE%E5%86%8D%E5%B8%B0%E6%9C%80%E9%81%A9%E5%8C%96
<自分用メモ>
末尾呼び出し: ある関数fのリターン前の最後の計算が関数gの呼び出しであるということ
末尾再帰最適化: 末尾再帰になっているコードに対してコンパイラが行う最適化で、コールスタックサイズの縮小が期待される。具体的には、
func f(x1) {
x2 := x1 * 2
return f(x2)
}
みたいなコードがあったとして、return f(x2)の時にf(x1)の現状をスタックフレームとしてコールスタックに積む代わりに、func f(x1)の行にジャンプして引数をx2に更新する。といった動作をする
末尾再帰最適化はあくまで言語処理系が行う最適化であるので、最適化してくれない処理系を使っているときに末尾再帰を書いてもパフォーマンス上の利点はない
Goは末尾再帰最適化がないと思っていたが、一部あるらしい
https://groups.google.com/g/golang-nuts/c/0oIZPHhrDzY/m/2nCpUZDKZAAJ
JSはES6でサポートされるようになったらしい
|
@oda |
|
@TORUS0818 |
inorderで一つ前の値をなんらかの方法で再帰関数に渡してあげれば良いです。 |
|
@liquo-rice func isValidBST(root *TreeNode) bool {
isValid, _ := isValidBSTHelper(root, nil)
return isValid
}
func isValidBSTHelper(node *TreeNode, previousValue *int) (bool, *int) {
value := previousValue
if node.Left != nil {
isValid, v := isValidBSTHelper(node.Left, previousValue)
if !isValid {
return false, nil
}
value = v
}
if value != nil && node.Val <= *value {
return false, nil
}
value = &node.Val
if node.Right != nil {
isValid, v := isValidBSTHelper(node.Right, value)
if !isValid {
return false, nil
}
value = v
}
return true, value
} |
|
このようにもできますね。 type optionalInt struct {
HasValue bool
Value int
}
func isValidBST(root *TreeNode) bool {
previousValue := optionalInt{false, 0}
return isValidBSTHelper(root, &previousValue)
}
func isValidBSTHelper(node *TreeNode, previousValue *optionalInt) bool {
if node == nil {
return true
}
if (!isValidBSTHelper(node.Left, previousValue)) {
return false
}
if previousValue.HasValue && node.Val <= previousValue.Value {
return false
}
previousValue.HasValue = true
previousValue.Value = node.Val
return isValidBSTHelper(node.Right, previousValue)
} |
| return subtree{valid: true, maxValue: nil, minValue: nil} | ||
| } | ||
| leftSubtree := isValidBSTRecursively(root.Left) | ||
| isLeftSubtreeValid := leftSubtree.valid && (leftSubtree.maxValue == nil || *leftSubtree.maxValue < root.Val) |
There was a problem hiding this comment.
leftSubtree.maxValue == nil || *leftSubtree.maxValue < root.Valというのが、ここにもL115にも出ているのはやや気になりました。
ここだけで関数化するかというと、なかなか難しいですね
| for node != nil { | ||
| stack = append(stack, node) | ||
| node = node.Left | ||
| } |
|
話が出てたので、末尾再帰で再帰関数が1つのコードを書いてみました(自分の練習がてら) class Solution:
def isValidBST(self, root: Optional[TreeNode]) -> bool:
stack = []
return self.is_valid(root, stack, None)
def is_valid(self, node, stack, prev_val):
if not stack and not node:
return True
while node:
stack.append(node)
node = node.left
node = stack.pop()
if prev_val is not None and prev_val >= node.val:
return False
prev_val = node.val
return self.is_valid(node.right, stack, prev_val) |
|
@liquo-rice ↑これ違いますね。ポインタを使っていると、n番目の再帰のpreviousValueとしてヌルポインタが渡されていると、それ以降の探索でpreviousValueが任意の値へのポインタとなってもヌルポインタに影響がない |
|
@nittoco func isValidBST(root *TreeNode) bool {
stack := []*TreeNode{}
return isValidBSTHelper(root, stack, nil)
}
func isValidBSTHelper(node *TreeNode, stack []*TreeNode, previousValue *int) bool {
if len(stack) == 0 && node == nil {
return true
}
for node != nil {
stack = append(stack, node)
node = node.Left
}
top := stack[len(stack)-1]
stack = stack[:len(stack)-1]
if previousValue != nil && top.Val <= *previousValue {
return false
}
previousValue = &top.Val
return isValidBSTHelper(top.Right, stack, previousValue)
} |
https://leetcode.com/problems/validate-binary-search-tree/description/