完整 Scala 2.13 参考实现
以下单文件不依赖第三方库,可保存为 MockLinearGraph.scala。
import scala.collection.mutable
object MockLinearGraph {
def cholesky(
a: Array[Array[Double]],
eps: Double = 1e-12
): Array[Array[Double]] = {
require(eps >= 0.0, "eps must be nonnegative")
val n = a.length
require(a.forall(_.length == n), "square matrix required")
var i = 0
while (i < n) {
var j = 0
while (j < n) {
require(
math.abs(a(i)(j) - a(j)(i)) <= eps,
"symmetric matrix required"
)
j += 1
}
i += 1
}
val l = Array.ofDim[Double](n, n)
var column = 0
while (column < n) {
var diagonalSum = 0.0
var k = 0
while (k < column) {
diagonalSum += l(column)(k) * l(column)(k)
k += 1
}
val residual = a(column)(column) - diagonalSum
require(residual > eps, "matrix is not SPD")
l(column)(column) = math.sqrt(residual)
i = column + 1
while (i < n) {
var cross = 0.0
k = 0
while (k < column) {
cross += l(i)(k) * l(column)(k)
k += 1
}
l(i)(column) =
(a(i)(column) - cross) / l(column)(column)
i += 1
}
column += 1
}
l
}
def shortestPath(
adjacency: Map[String, Seq[String]],
source: String,
target: String,
maxDepth: Int
): Option[List[String]] = {
require(maxDepth >= 0, "maxDepth must be nonnegative")
if (source == target) return Some(List(source))
val queue = mutable.Queue.empty[(String, Int)]
val seen = mutable.Set.empty[String]
val parent = mutable.Map.empty[String, String]
queue.enqueue((source, 0))
seen += source
while (queue.nonEmpty) {
val (u, depth) = queue.dequeue()
if (depth < maxDepth) {
val neighbors = adjacency.getOrElse(u, Seq.empty).distinct.sorted
neighbors.foreach { v =>
if (!seen(v)) {
seen += v
parent(v) = u
if (v == target)
return Some(reconstruct(parent.toMap, source, target))
queue.enqueue((v, depth + 1))
}
}
}
}
None
}
private def reconstruct(
parent: Map[String, String],
source: String,
target: String
): List[String] = {
var path = List(target)
var current = target
while (current != source) {
current = parent(current)
path = current :: path
}
path
}
private def multiplyLLT(l: Array[Array[Double]]): Array[Array[Double]] = {
val n = l.length
val out = Array.ofDim[Double](n, n)
var i = 0
while (i < n) {
var j = 0
while (j < n) {
var k = 0
while (k < n) {
out(i)(j) += l(i)(k) * l(j)(k)
k += 1
}
j += 1
}
i += 1
}
out
}
private def close(x: Double, y: Double): Boolean =
math.abs(x - y) <= 1e-9
def main(args: Array[String]): Unit = {
val a = Array(
Array(25.0, 15.0, -5.0),
Array(15.0, 18.0, 0.0),
Array(-5.0, 0.0, 11.0)
)
val l = cholesky(a)
val expected = Array(
Array(5.0, 0.0, 0.0),
Array(3.0, 3.0, 0.0),
Array(-1.0, 1.0, 3.0)
)
for (i <- a.indices; j <- a.indices)
assert(close(l(i)(j), expected(i)(j)))
val rebuilt = multiplyLLT(l)
for (i <- a.indices; j <- a.indices)
assert(close(rebuilt(i)(j), a(i)(j)))
val graph = Map(
"A" -> Seq("B", "C"),
"B" -> Seq("D"),
"C" -> Seq("D"),
"D" -> Seq("E")
)
assert(shortestPath(graph, "A", "E", 3).contains(List("A", "B", "D", "E")))
assert(shortestPath(graph, "A", "E", 2).isEmpty)
assert(shortestPath(Map("A" -> Seq("A", "B")), "A", "B", 1)
.contains(List("A", "B")))
assert(shortestPath(graph, "A", "A", 0).contains(List("A")))
println("MockLinearGraph tests passed")
}
}
复杂度与循环不变量
Cholesky
- 时间 Θ(n³),输出状态 Θ(n²),主导 flop 约 n³/3。
- 完成第 j 列后,前 j+1 个 leading rows/columns 已满足对应的 A=LLT 元素方程。
- 上三角保持 0;每个已写 diagonal 严格为正。
BFS
- 邻接表时间 Θ(V+E),空间 Θ(V);深度限制时只遍历半径内子图。
- queue 中 depth 非降;顶点入队时立刻 seen,因此首次发现层即最短距离。
parent(v) 指向上一层,回溯路径无环。
Spark frontier 一步
// 关系语义;列名按实际 schema 替换
val candidates = frontier
.join(edges, frontier("artist") === edges("src"))
.select(frontier("source"), edges("dst").as("artist"))
.dropDuplicates("source", "artist")
val next = candidates.join(
visited,
Seq("source", "artist"),
"left_anti"
)
val visitedNext = visited.union(next).dropDuplicates("source", "artist")
以上是 DataFrame 关系骨架,不是本地 BFS 的必要依赖。核心顺序是 join → 轮内去重 → left-anti visited → union。