14
Branching in Haskell
Pure recursion with sets. Haskell's Data.Set keeps the code as close to the math as code gets.
- Represent the graph as a set of edges, where an edge is a pair of vertices. The whole algorithm will be set operations on this structure — no mutation, no aliasing.
import qualified Data.Set as S type Vertex = Int type Edge = (Vertex, Vertex) type Graph = S.Set Edge - The classic FPT branch: pick any uncovered edge (u,v). Either u is in the cover or v is. Recurse on both, return the smaller result. The recursion depth is bounded by k, not by n.
vcover :: Int -> Graph -> Maybe (S.Set Vertex) vcover k es | S.null es = Just S.empty | k == 0 = Nothing | otherwise = let (u, v) = S.findMin es rm x = S.filter (\(a,b) -> a /= x && b /= x) es tryU = S.insert u <$> vcover (k-1) (rm u) tryV = S.insert v <$> vcover (k-1) (rm v) in tryU <|> tryV - Wrap it to find the minimum k that works. We start at 0 and increase; the first hit is optimal. (For real workloads use the kernelization tricks too, but they don't change the shape.)
import Control.Applicative ((<|>)) minCover :: Graph -> S.Set Vertex minCover g = head [c | k <- [0..], Just c <- [vcover k g]] - Try it. The whole solver fits in a third of a screen and reads as the algorithm's English description. That is what Haskell buys you for problems shaped like this.
main = do let g = S.fromList [(1,2),(2,3),(3,4),(4,1),(2,4)] print (minCover g) -- fromList [2,4]