您忘记在输出中包含B105。这将在输出中增加 10 行 -
A10 B105 C1
A10 B105 C10
A10 B105 C11
A10 B105 C12
A10 B105 C13
A11 B105 C1
A11 B105 C10
A11 B105 C11
A11 B105 C12
A11 B105 C13
撇开这个错误不谈,这个问题与cartesian product 有一些小区别,cartesian product 有许多众所周知的算法。
关键区别在于leaf 节点永远不会出现在 tree 节点之后。一种简单的算法会生成所有树节点的笛卡尔积,然后删除leafs 出现在trees 之后的那些。
但我们可以做得更好。当遇到tree 节点时,我们可以智能地向前跳过,而不是浪费地访问将从输出中删除的路径 -
JavaScript
这是一个使用 JavaScript 的快速实现 -
const traverse = ([ t, ...more ], r = []) =>
// base case: empty t
t === undefined
? [ r ]
// inductive: t.leaf
: t.leaf
? traverse(more, [...r, t.value])
// inductive: t.tree
: [ [ ...r, t.value, ...more.map(t => t.value) ] // <--
, ...t.children.flatMap(c => traverse([ c, ...more ], r))
]
traverse 函数的输入是 leaf 或 tree 节点的数组 -
const leaf = (value) =>
({ leaf, value })
const tree = (value, ...children) =>
({ tree, value, children })
鉴于您原始帖子中的树木 -
A1 B1 C1
A10 B10 C10
A11 B100 C11
B101 C12
B102 C13
B103
B104
B105
B11
B110
B111
B112
B113
B114
我们可以像这样使用tree 和leaf 来表示它们-
const treeA =
tree("A1", leaf("A10"), leaf("A11"))
const treeB =
tree
( "B1"
, tree("B10", leaf("B100"), leaf("B101"), leaf("B102"), leaf("B103"), leaf("B104"), leaf("B105"))
, tree("B11", leaf("B110"), leaf("B111"), leaf("B112"), leaf("B113"), leaf("B114"))
)
const treeC =
tree("C1", leaf("C10"), leaf("C11"), leaf("C12"), leaf("C13"))
const result =
traverse([ treeA, treeB, treeC ]) // [ [ "A1 B1 C1" ], [ ... ], ... ]
.map(path => path.join(" ")) // [ "A1 B1 C1", "A10 B1 C1", ... ]
console.log(result.join("\n")) // "A1 B1 C1\nA10 B1 C1\n..."
console.log(result.length) // 117
展开下面的sn-p,在浏览器中验证结果-
const leaf = (value) =>
({ leaf, value })
const tree = (value, ...children) =>
({ tree, value, children })
const traverse = ([ t, ...more ], r = []) =>
// base case: empty t
t === undefined
? [ r ]
// inductive: t.leaf
: t.leaf
? traverse(more, [...r, t.value])
// inductive: t.tree
: [ [ ...r, t.value, ...more.map(t => t.value) ]
, ...t.children.flatMap(c => traverse([ c, ...more ], r))
]
const treeA =
tree("A1", leaf("A10"), leaf("A11"))
const treeB =
tree
( "B1"
, tree("B10", leaf("B100"), leaf("B101"), leaf("B102"), leaf("B103"), leaf("B104"), leaf("B105"))
, tree("B11", leaf("B110"), leaf("B111"), leaf("B112"), leaf("B113"), leaf("B114"))
)
const treeC =
tree("C1", leaf("C10"), leaf("C11"), leaf("C12"), leaf("C13"))
const result =
traverse([ treeA, treeB, treeC ])
.map(path => path.join(" "))
console.log(result.join("\n"))
console.log(result.length)
输出 -
A1 B1 C1
A10 B1 C1
A10 B10 C1
A10 B100 C1
A10 B100 C10
A10 B100 C11
A10 B100 C12
A10 B100 C13
A10 B101 C1
A10 B101 C10
A10 B101 C11
A10 B101 C12
A10 B101 C13
A10 B102 C1
A10 B102 C10
A10 B102 C11
A10 B102 C12
A10 B102 C13
A10 B103 C1
A10 B103 C10
A10 B103 C11
A10 B103 C12
A10 B103 C13
A10 B104 C1
A10 B104 C10
A10 B104 C11
A10 B104 C12
A10 B104 C13
A10 B105 C1
A10 B105 C10
A10 B105 C11
A10 B105 C12
A10 B105 C13
A10 B11 C1
A10 B110 C1
A10 B110 C10
A10 B110 C11
A10 B110 C12
A10 B110 C13
A10 B111 C1
A10 B111 C10
A10 B111 C11
A10 B111 C12
A10 B111 C13
A10 B112 C1
A10 B112 C10
A10 B112 C11
A10 B112 C12
A10 B112 C13
A10 B113 C1
A10 B113 C10
A10 B113 C11
A10 B113 C12
A10 B113 C13
A10 B114 C1
A10 B114 C10
A10 B114 C11
A10 B114 C12
A10 B114 C13
A11 B1 C1
A11 B10 C1
A11 B100 C1
A11 B100 C10
A11 B100 C11
A11 B100 C12
A11 B100 C13
A11 B101 C1
A11 B101 C10
A11 B101 C11
A11 B101 C12
A11 B101 C13
A11 B102 C1
A11 B102 C10
A11 B102 C11
A11 B102 C12
A11 B102 C13
A11 B103 C1
A11 B103 C10
A11 B103 C11
A11 B103 C12
A11 B103 C13
A11 B104 C1
A11 B104 C10
A11 B104 C11
A11 B104 C12
A11 B104 C13
A11 B105 C1
A11 B105 C10
A11 B105 C11
A11 B105 C12
A11 B105 C13
A11 B11 C1
A11 B110 C1
A11 B110 C10
A11 B110 C11
A11 B110 C12
A11 B110 C13
A11 B111 C1
A11 B111 C10
A11 B111 C11
A11 B111 C12
A11 B111 C13
A11 B112 C1
A11 B112 C10
A11 B112 C11
A11 B112 C12
A11 B112 C13
A11 B113 C1
A11 B113 C10
A11 B113 C11
A11 B113 C12
A11 B113 C13
A11 B114 C1
A11 B114 C10
A11 B114 C11
A11 B114 C12
A11 B114 C13
117 // results.length
从 JavaScript 到 Java
我不懂 Java,但我可以帮助重写上面的程序,用一种可能更容易翻译的方式 -
JavaScript Java
================================================================
Array.prototype.concat List.AddAll, Stream.concat
Array.prototype.map Stream.map
Array.prototype.flatMap Stream.flatMap
Array.prototype.join String.join
(c => ___) Lambda (c -> ___)
[ foo, ...bar ] (no equivalent, see below)
const traverse = (trees = []) =>
// call traverseHelper with trees and initial path for all nodes, []
traverseHelper(trees, [])
function traverseHelper ([ t, ...more ], r = [])
{ // when no t
if (t === undefined)
return [ r ]
// when t.leaf
else if (t.leaf)
return traverseHelper(more, [ ...r, t.value ])
// when t.tree
else
{ // when t.tree is encountered, do not add t.value to the children paths
// simply copy the .value of each tree to the path for this node
const path = r.concat([ t.value ]).concat(more.map(t => t.value))
// return the path for this node, combined wit the results for
// the children nodes.
return [ path ]
.concat(t.children.flatMap(c => traverseHelper([ c, ...more ], r)))
}
}
据我所知,Java 不支持spread syntax。这些只是常见数组操作的语法糖 -
function foo(x, y, ...z) {
console.log("foo x:", String(x))
console.log("foo y:", String(y))
console.log("foo z:", String(z))
}
function bar([ x, ...more ]) {
console.log("bar x:", String(x))
console.log("bar more:", String(more))
}
const a = [1,2,3]
const b = 4
const c = [5,6]
foo(...a, b, ...c) // <-- foo(1,2,3,4,5,6)
// x: 1
// y: 2
// z: 3,4,5,6
const d = [...a, b, ...c] // <-- [1,2,3,4,5,6]
bar(d)
// x: 1
// more: 2,3,4,5,6
球拍/方案
Racket 是讨论和分享算法的绝佳语言,因为它几乎没有语法,而且程序由简单的部分组成 -
#lang racket
(define (traverse ts (r null))
(cond
;; base case: empty
((null? ts)
(list r))
;; inductive: leaf
((null? (cdar ts))
(traverse (cdr ts)
(cons (caar ts) r)))
;; inductive: tree
(else
(cons (append (reverse (map car ts)) r)
(append-map (lambda (c)
(traverse (cons c (cdr ts)) r))
(cdar ts))))))
这几乎是对 JavaScript 代码的直接翻译,只是路径结果是以相反的顺序构造的。
(define a
'(a1 (a10) (a11)))
(define b
'(b1 (b10 (b100) (b101) (b102) (b103) (b104) (b105))
(b11 (b110) (b111) (b112) (b113) (b114))))
(define c
'(c1 (c10) (c11) (c12) (c13)))
(define result
(map reverse (traverse (list a b c))))
result
(map reverse ...) 用于显示使用正确顺序的结果 -
'((a1 b1 c1)
(a10 b1 c1)
(a10 b10 c1)
(a10 b100 c1)
(a10 b100 c10)
(a10 b100 c11)
(a10 b100 c12)
(a10 b100 c13)
(a10 b101 c1)
(a10 b101 c10)
(a10 b101 c11)
(a10 b101 c12)
(a10 b101 c13)
(a10 b102 c1)
(a10 b102 c10)
(a10 b102 c11)
(a10 b102 c12)
(a10 b102 c13)
(a10 b103 c1)
(a10 b103 c10)
(a10 b103 c11)
(a10 b103 c12)
(a10 b103 c13)
(a10 b104 c1)
(a10 b104 c10)
(a10 b104 c11)
(a10 b104 c12)
(a10 b104 c13)
(a10 b105 c1)
(a10 b105 c10)
(a10 b105 c11)
(a10 b105 c12)
(a10 b105 c13)
(a10 b11 c1)
(a10 b110 c1)
(a10 b110 c10)
(a10 b110 c11)
(a10 b110 c12)
(a10 b110 c13)
(a10 b111 c1)
(a10 b111 c10)
(a10 b111 c11)
(a10 b111 c12)
(a10 b111 c13)
(a10 b112 c1)
(a10 b112 c10)
(a10 b112 c11)
(a10 b112 c12)
(a10 b112 c13)
(a10 b113 c1)
(a10 b113 c10)
(a10 b113 c11)
(a10 b113 c12)
(a10 b113 c13)
(a10 b114 c1)
(a10 b114 c10)
(a10 b114 c11)
(a10 b114 c12)
(a10 b114 c13)
(a11 b1 c1)
(a11 b10 c1)
(a11 b100 c1)
(a11 b100 c10)
(a11 b100 c11)
(a11 b100 c12)
(a11 b100 c13)
(a11 b101 c1)
(a11 b101 c10)
(a11 b101 c11)
(a11 b101 c12)
(a11 b101 c13)
(a11 b102 c1)
(a11 b102 c10)
(a11 b102 c11)
(a11 b102 c12)
(a11 b102 c13)
(a11 b103 c1)
(a11 b103 c10)
(a11 b103 c11)
(a11 b103 c12)
(a11 b103 c13)
(a11 b104 c1)
(a11 b104 c10)
(a11 b104 c11)
(a11 b104 c12)
(a11 b104 c13)
(a11 b105 c1)
(a11 b105 c10)
(a11 b105 c11)
(a11 b105 c12)
(a11 b105 c13)
(a11 b11 c1)
(a11 b110 c1)
(a11 b110 c10)
(a11 b110 c11)
(a11 b110 c12)
(a11 b110 c13)
(a11 b111 c1)
(a11 b111 c10)
(a11 b111 c11)
(a11 b111 c12)
(a11 b111 c13)
(a11 b112 c1)
(a11 b112 c10)
(a11 b112 c11)
(a11 b112 c12)
(a11 b112 c13)
(a11 b113 c1)
(a11 b113 c10)
(a11 b113 c11)
(a11 b113 c12)
(a11 b113 c13)
(a11 b114 c1)
(a11 b114 c10)
(a11 b114 c11)
(a11 b114 c12)
(a11 b114 c13))