56 lines
1.3 KiB
Kotlin
56 lines
1.3 KiB
Kotlin
|
package net.shadowfacts.cacao.util
|
||
|
|
||
|
import java.util.*
|
||
|
import kotlin.NoSuchElementException
|
||
|
|
||
|
/**
|
||
|
* A linear time algorithm for finding the lowest common ancestor of two nodes in a graph.
|
||
|
* Based on https://stackoverflow.com/a/6342546/4731558
|
||
|
*
|
||
|
* Works be finding the path from each node back to the root node.
|
||
|
* The LCA will then be the node after which the paths diverge.
|
||
|
*
|
||
|
* @author shadowfacts
|
||
|
*/
|
||
|
object LowestCommonAncestor {
|
||
|
|
||
|
fun <Node> find(node1: Node, node2: Node, parent: Node.() -> Node?): Node? {
|
||
|
@Suppress("NAME_SHADOWING") var node1: Node? = node1
|
||
|
@Suppress("NAME_SHADOWING") var node2: Node? = node2
|
||
|
|
||
|
val parent1 = LinkedList<Node>()
|
||
|
while (node1 != null) {
|
||
|
parent1.push(node1)
|
||
|
node1 = node1.parent()
|
||
|
}
|
||
|
|
||
|
val parent2 = LinkedList<Node>()
|
||
|
while (node2 != null) {
|
||
|
parent2.push(node2)
|
||
|
node2 = node2.parent()
|
||
|
}
|
||
|
|
||
|
// paths don't converge on the same root element
|
||
|
if (parent1.first != parent2.first) {
|
||
|
return null
|
||
|
}
|
||
|
|
||
|
var oldNode: Node? = null
|
||
|
while (node1 == node2 && parent1.isNotEmpty() && parent2.isNotEmpty()) {
|
||
|
oldNode = node1
|
||
|
node1 = parent1.popOrNull()
|
||
|
node2 = parent2.popOrNull()
|
||
|
}
|
||
|
return if (node1 == node2) node1!!
|
||
|
else oldNode!!
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
private fun <T> LinkedList<T>.popOrNull(): T? {
|
||
|
return try {
|
||
|
pop()
|
||
|
} catch (e: NoSuchElementException) {
|
||
|
null
|
||
|
}
|
||
|
}
|