Add Lowest Common Ancestor algorithm
This commit is contained in:
parent
83bd29ecb8
commit
51528c4cbe
|
@ -0,0 +1,56 @@
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
package net.shadowfacts.cacao.util
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.Assertions.assertNull
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author shadowfacts
|
||||||
|
*/
|
||||||
|
class LCATest {
|
||||||
|
|
||||||
|
class Node(val name: String, val parent: Node?)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testDirectParent() {
|
||||||
|
val parent = Node("parent", null)
|
||||||
|
val child = Node("child", parent)
|
||||||
|
assertEquals(parent, LowestCommonAncestor.find(parent, child, Node::parent))
|
||||||
|
assertEquals(parent, LowestCommonAncestor.find(child, parent, Node::parent))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testSiblings() {
|
||||||
|
val root = Node("root", null)
|
||||||
|
val a = Node("a", root)
|
||||||
|
val b = Node("b", root)
|
||||||
|
assertEquals(root, LowestCommonAncestor.find(a, b, Node::parent))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testBetweenSubtrees() {
|
||||||
|
// ┌────┐
|
||||||
|
// │root│
|
||||||
|
// └────┘
|
||||||
|
// ╱ ╲
|
||||||
|
// ╱ ╲
|
||||||
|
// ┌─┐ ┌─┐
|
||||||
|
// │A│ │B│
|
||||||
|
// └─┘ └─┘
|
||||||
|
// ╱ ╲ ╱ ╲
|
||||||
|
// ╱ ╲ ╱ ╲
|
||||||
|
// ┌─┐ ┌─┐┌─┐ ┌─┐
|
||||||
|
// │C│ │D││E│ │F│
|
||||||
|
// └─┘ └─┘└─┘ └─┘
|
||||||
|
val root = Node("root", null)
|
||||||
|
|
||||||
|
val a = Node("a", root)
|
||||||
|
val c = Node("c", a)
|
||||||
|
val d = Node("d", a)
|
||||||
|
|
||||||
|
val b = Node("b", root)
|
||||||
|
val e = Node("e", b)
|
||||||
|
val f = Node("f", b)
|
||||||
|
|
||||||
|
assertEquals(a, LowestCommonAncestor.find(c, d, Node::parent))
|
||||||
|
assertEquals(root, LowestCommonAncestor.find(c, b, Node::parent))
|
||||||
|
assertEquals(root, LowestCommonAncestor.find(d, e, Node::parent))
|
||||||
|
assertEquals(root, LowestCommonAncestor.find(c, root, Node::parent))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testBetweenDisjointTrees() {
|
||||||
|
val a = Node("a", null)
|
||||||
|
val b = Node("b", a)
|
||||||
|
|
||||||
|
val c = Node("c", null)
|
||||||
|
val d = Node("d", c)
|
||||||
|
|
||||||
|
assertNull(LowestCommonAncestor.find(a, d, Node::parent))
|
||||||
|
assertNull(LowestCommonAncestor.find(b, c, Node::parent))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue